@ema.co/mcp-toolkit 2026.3.23-3 → 2026.3.24
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-COMMERCIAL.md +45 -0
- package/dist/config/generated/stage-eligibility.snapshot.js +118 -10
- package/dist/config/profile.js +4 -2
- package/dist/config/stage-resolution.js +2 -0
- package/dist/config/tool-guidance.js +6 -0
- package/dist/knowledge/gcp-auth.js +123 -17
- package/dist/knowledge/guidance-cache.js +24 -0
- package/dist/knowledge/search-client.js +95 -8
- package/dist/mcp/guidance.js +10 -0
- package/dist/mcp/handlers/config/index.js +12 -3
- package/dist/mcp/handlers/debug/formatters.js +1 -1
- package/dist/mcp/handlers/feedback/global-analysis-gcs.js +11 -3
- package/dist/mcp/handlers/feedback/index.js +15 -2
- package/dist/mcp/handlers/feedback/outbox.js +55 -0
- package/dist/mcp/handlers/index.js +2 -2
- package/dist/mcp/handlers/knowledge/index.js +11 -4
- package/dist/mcp/handlers/persona/create.js +14 -4
- package/dist/mcp/handlers/response-actions.js +38 -0
- package/dist/mcp/handlers/template/adapter.js +3 -3
- package/dist/mcp/handlers/template/index.js +14 -9
- package/dist/mcp/handlers/workflow/adapter.js +6 -4
- package/dist/mcp/handlers/workflow/deploy.js +1 -1
- package/dist/mcp/handlers/workflow/index.js +2 -2
- package/dist/mcp/handlers/workflow/optimize.js +1 -1
- package/dist/mcp/knowledge.js +2 -10
- package/dist/mcp/resources-dynamic.js +1 -1
- package/dist/mcp/server.js +16 -14
- package/dist/mcp/tools.js +7 -4
- package/dist/sdk/generated/agent-catalog.js +173 -0
- package/package.json +4 -2
- package/dist/mcp/handlers/catalog/index.js +0 -267
- package/dist/mcp/handlers/reference/index.js +0 -462
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Commercial License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ema Unlimited, Inc.
|
|
4
|
+
|
|
5
|
+
This software is available under a commercial license for organizations
|
|
6
|
+
that wish to use, modify, or distribute it without the obligations of
|
|
7
|
+
the GNU Affero General Public License (AGPL) v3.
|
|
8
|
+
|
|
9
|
+
## Grant of License
|
|
10
|
+
|
|
11
|
+
Subject to payment of applicable fees and compliance with the terms
|
|
12
|
+
of a written commercial agreement, Ema Unlimited, Inc. grants you a
|
|
13
|
+
non-exclusive, non-transferable license to:
|
|
14
|
+
|
|
15
|
+
- Use the software in proprietary or closed-source environments
|
|
16
|
+
- Modify the software without public disclosure
|
|
17
|
+
- Deploy the software as part of a hosted or managed service
|
|
18
|
+
- Integrate the software into internal or commercial systems
|
|
19
|
+
|
|
20
|
+
## Restrictions
|
|
21
|
+
|
|
22
|
+
Without an applicable commercial agreement, you may NOT:
|
|
23
|
+
|
|
24
|
+
- Offer the software as a hosted or managed service
|
|
25
|
+
- Expose the software’s network interfaces, handlers, or internal
|
|
26
|
+
port-based APIs in production
|
|
27
|
+
- Create derivative works that are not released under AGPLv3
|
|
28
|
+
- Circumvent licensing, feature controls, or access restrictions
|
|
29
|
+
|
|
30
|
+
## Ownership
|
|
31
|
+
|
|
32
|
+
All rights, title, and interest in the software remain with
|
|
33
|
+
Ema Unlimited, Inc.. No rights are granted except as expressly stated.
|
|
34
|
+
|
|
35
|
+
## Warranty Disclaimer
|
|
36
|
+
|
|
37
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
38
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO MERCHANTABILITY, FITNESS FOR A
|
|
39
|
+
PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
|
|
40
|
+
|
|
41
|
+
## Contact
|
|
42
|
+
|
|
43
|
+
For licensing inquiries, please contact:
|
|
44
|
+
|
|
45
|
+
legal@ema.co or steven.poitras@ema.co
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
* AUTO-GENERATED — Do not edit manually.
|
|
3
3
|
* Run: npm run generate:stage-snapshot
|
|
4
4
|
*
|
|
5
|
-
* Generated from
|
|
6
|
-
*
|
|
5
|
+
* Generated from 57 catalog actions.
|
|
6
|
+
* 29 stages, 57 actions with eligibility.
|
|
7
7
|
* Overrides applied: abstain_action, custom_agent, document_synthesis, entity_extraction_with_documents, fixed_response, general_hitl, json_mapper, respond_for_external_actions, search, text_categorizer
|
|
8
8
|
*/
|
|
9
9
|
export const STAGE_ELIGIBILITY_SNAPSHOT = {
|
|
10
|
-
"catalogSize":
|
|
11
|
-
"stageCount":
|
|
12
|
-
"actionCount":
|
|
10
|
+
"catalogSize": 57,
|
|
11
|
+
"stageCount": 29,
|
|
12
|
+
"actionCount": 57,
|
|
13
13
|
"overridesApplied": [
|
|
14
14
|
"abstain_action",
|
|
15
15
|
"custom_agent",
|
|
@@ -32,7 +32,11 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
|
|
|
32
32
|
"chat_categorizer",
|
|
33
33
|
"conversation_to_search_query",
|
|
34
34
|
"document_categorizer",
|
|
35
|
-
"
|
|
35
|
+
"feedback_categorizer",
|
|
36
|
+
"make_response_public",
|
|
37
|
+
"text_categorizer",
|
|
38
|
+
"thread_categorizer",
|
|
39
|
+
"thread_summarizer"
|
|
36
40
|
],
|
|
37
41
|
"refine": [
|
|
38
42
|
"chat_categorizer",
|
|
@@ -56,6 +60,7 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
|
|
|
56
60
|
"information_redacter",
|
|
57
61
|
"insurance_claim_summarizer",
|
|
58
62
|
"legal_expert",
|
|
63
|
+
"market_research",
|
|
59
64
|
"medical_record_summarizer",
|
|
60
65
|
"medical_research_assistant",
|
|
61
66
|
"phishing_email_detector",
|
|
@@ -81,6 +86,7 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
|
|
|
81
86
|
],
|
|
82
87
|
"handle": [
|
|
83
88
|
"call_llm",
|
|
89
|
+
"chart_creator",
|
|
84
90
|
"custom_agent",
|
|
85
91
|
"fixed_response",
|
|
86
92
|
"generate_document",
|
|
@@ -90,11 +96,13 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
|
|
|
90
96
|
"respond": [
|
|
91
97
|
"audit_evidence_verifier",
|
|
92
98
|
"call_llm",
|
|
99
|
+
"chart_creator",
|
|
93
100
|
"compliance_document_analyzer",
|
|
94
101
|
"custom_agent",
|
|
95
102
|
"cybersecurity_expert",
|
|
96
103
|
"document_synthesis",
|
|
97
104
|
"email_writer",
|
|
105
|
+
"feedback_categorizer",
|
|
98
106
|
"financial_risk_assessor",
|
|
99
107
|
"financial_statement_analyzer",
|
|
100
108
|
"fixed_response",
|
|
@@ -102,17 +110,23 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
|
|
|
102
110
|
"information_redacter",
|
|
103
111
|
"insurance_claim_summarizer",
|
|
104
112
|
"legal_expert",
|
|
113
|
+
"make_response_public",
|
|
114
|
+
"market_research",
|
|
105
115
|
"medical_record_summarizer",
|
|
106
116
|
"medical_research_assistant",
|
|
107
117
|
"phishing_email_detector",
|
|
118
|
+
"qa_over_sql",
|
|
108
119
|
"respond_with_sources",
|
|
109
120
|
"sales_intelligence",
|
|
110
121
|
"sentiment_analyzer",
|
|
111
122
|
"tax_advisor",
|
|
112
|
-
"technical_support"
|
|
123
|
+
"technical_support",
|
|
124
|
+
"thread_categorizer",
|
|
125
|
+
"thread_summarizer"
|
|
113
126
|
],
|
|
114
127
|
"reason": [
|
|
115
128
|
"call_llm",
|
|
129
|
+
"chart_creator",
|
|
116
130
|
"custom_agent",
|
|
117
131
|
"fixed_response",
|
|
118
132
|
"generate_document",
|
|
@@ -120,6 +134,7 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
|
|
|
120
134
|
],
|
|
121
135
|
"draft": [
|
|
122
136
|
"call_llm",
|
|
137
|
+
"chart_creator",
|
|
123
138
|
"custom_agent",
|
|
124
139
|
"fixed_response",
|
|
125
140
|
"generate_document",
|
|
@@ -128,6 +143,7 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
|
|
|
128
143
|
],
|
|
129
144
|
"compose": [
|
|
130
145
|
"call_llm",
|
|
146
|
+
"chart_creator",
|
|
131
147
|
"custom_agent",
|
|
132
148
|
"fixed_response",
|
|
133
149
|
"generate_document",
|
|
@@ -136,13 +152,19 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
|
|
|
136
152
|
],
|
|
137
153
|
"summarize": [
|
|
138
154
|
"call_llm",
|
|
155
|
+
"chart_creator",
|
|
139
156
|
"custom_agent",
|
|
157
|
+
"feedback_categorizer",
|
|
140
158
|
"fixed_response",
|
|
141
159
|
"generate_document",
|
|
142
|
-
"
|
|
160
|
+
"make_response_public",
|
|
161
|
+
"respond_with_sources",
|
|
162
|
+
"thread_categorizer",
|
|
163
|
+
"thread_summarizer"
|
|
143
164
|
],
|
|
144
165
|
"resolve": [
|
|
145
166
|
"call_llm",
|
|
167
|
+
"chart_creator",
|
|
146
168
|
"custom_agent",
|
|
147
169
|
"fixed_response",
|
|
148
170
|
"generate_document",
|
|
@@ -152,6 +174,7 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
|
|
|
152
174
|
"analyze": [
|
|
153
175
|
"audit_evidence_verifier",
|
|
154
176
|
"call_llm",
|
|
177
|
+
"chart_creator",
|
|
155
178
|
"compliance_document_analyzer",
|
|
156
179
|
"custom_agent",
|
|
157
180
|
"financial_risk_assessor",
|
|
@@ -163,6 +186,7 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
|
|
|
163
186
|
"legal_expert",
|
|
164
187
|
"medical_record_summarizer",
|
|
165
188
|
"medical_research_assistant",
|
|
189
|
+
"qa_over_sql",
|
|
166
190
|
"respond_with_sources",
|
|
167
191
|
"sentiment_analyzer",
|
|
168
192
|
"tax_advisor"
|
|
@@ -170,6 +194,7 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
|
|
|
170
194
|
"fallback": [
|
|
171
195
|
"abstain_action",
|
|
172
196
|
"call_llm",
|
|
197
|
+
"chart_creator",
|
|
173
198
|
"custom_agent",
|
|
174
199
|
"fixed_response",
|
|
175
200
|
"generate_document",
|
|
@@ -177,6 +202,7 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
|
|
|
177
202
|
],
|
|
178
203
|
"handle-failure": [
|
|
179
204
|
"call_llm",
|
|
205
|
+
"chart_creator",
|
|
180
206
|
"custom_agent",
|
|
181
207
|
"fixed_response",
|
|
182
208
|
"generate_document",
|
|
@@ -189,10 +215,15 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
|
|
|
189
215
|
"general_hitl"
|
|
190
216
|
],
|
|
191
217
|
"convert": [
|
|
218
|
+
"combine_search_results",
|
|
219
|
+
"combine_text_with_sources",
|
|
220
|
+
"convert_to_text",
|
|
221
|
+
"custom_code_agent",
|
|
192
222
|
"json_extractor",
|
|
193
223
|
"json_formatter",
|
|
194
224
|
"json_mapper",
|
|
195
|
-
"markdown_formatter"
|
|
225
|
+
"markdown_formatter",
|
|
226
|
+
"tags_extractor"
|
|
196
227
|
],
|
|
197
228
|
"act-on-success": [
|
|
198
229
|
"external_action_caller",
|
|
@@ -232,9 +263,21 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
|
|
|
232
263
|
"rule_validation_with_documents"
|
|
233
264
|
],
|
|
234
265
|
"format": [
|
|
266
|
+
"combine_search_results",
|
|
267
|
+
"combine_text_with_sources",
|
|
268
|
+
"convert_to_text",
|
|
269
|
+
"custom_code_agent",
|
|
235
270
|
"json_extractor",
|
|
236
271
|
"json_formatter",
|
|
237
|
-
"markdown_formatter"
|
|
272
|
+
"markdown_formatter",
|
|
273
|
+
"tags_extractor"
|
|
274
|
+
],
|
|
275
|
+
"merge": [
|
|
276
|
+
"combine_search_results",
|
|
277
|
+
"combine_text_with_sources",
|
|
278
|
+
"convert_to_text",
|
|
279
|
+
"custom_code_agent",
|
|
280
|
+
"tags_extractor"
|
|
238
281
|
]
|
|
239
282
|
},
|
|
240
283
|
"byAction": {
|
|
@@ -484,6 +527,71 @@ export const STAGE_ELIGIBILITY_SNAPSHOT = {
|
|
|
484
527
|
"markdown_formatter": [
|
|
485
528
|
"convert",
|
|
486
529
|
"format"
|
|
530
|
+
],
|
|
531
|
+
"chart_creator": [
|
|
532
|
+
"analyze",
|
|
533
|
+
"compose",
|
|
534
|
+
"draft",
|
|
535
|
+
"fallback",
|
|
536
|
+
"handle",
|
|
537
|
+
"handle-failure",
|
|
538
|
+
"reason",
|
|
539
|
+
"resolve",
|
|
540
|
+
"respond",
|
|
541
|
+
"summarize"
|
|
542
|
+
],
|
|
543
|
+
"combine_search_results": [
|
|
544
|
+
"convert",
|
|
545
|
+
"format",
|
|
546
|
+
"merge"
|
|
547
|
+
],
|
|
548
|
+
"combine_text_with_sources": [
|
|
549
|
+
"convert",
|
|
550
|
+
"format",
|
|
551
|
+
"merge"
|
|
552
|
+
],
|
|
553
|
+
"convert_to_text": [
|
|
554
|
+
"convert",
|
|
555
|
+
"format",
|
|
556
|
+
"merge"
|
|
557
|
+
],
|
|
558
|
+
"custom_code_agent": [
|
|
559
|
+
"convert",
|
|
560
|
+
"format",
|
|
561
|
+
"merge"
|
|
562
|
+
],
|
|
563
|
+
"tags_extractor": [
|
|
564
|
+
"convert",
|
|
565
|
+
"format",
|
|
566
|
+
"merge"
|
|
567
|
+
],
|
|
568
|
+
"thread_categorizer": [
|
|
569
|
+
"classify",
|
|
570
|
+
"respond",
|
|
571
|
+
"summarize"
|
|
572
|
+
],
|
|
573
|
+
"thread_summarizer": [
|
|
574
|
+
"classify",
|
|
575
|
+
"respond",
|
|
576
|
+
"summarize"
|
|
577
|
+
],
|
|
578
|
+
"make_response_public": [
|
|
579
|
+
"classify",
|
|
580
|
+
"respond",
|
|
581
|
+
"summarize"
|
|
582
|
+
],
|
|
583
|
+
"feedback_categorizer": [
|
|
584
|
+
"classify",
|
|
585
|
+
"respond",
|
|
586
|
+
"summarize"
|
|
587
|
+
],
|
|
588
|
+
"market_research": [
|
|
589
|
+
"act",
|
|
590
|
+
"respond"
|
|
591
|
+
],
|
|
592
|
+
"qa_over_sql": [
|
|
593
|
+
"analyze",
|
|
594
|
+
"respond"
|
|
487
595
|
]
|
|
488
596
|
}
|
|
489
597
|
};
|
package/dist/config/profile.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync, renameSync, mkdirSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
|
-
import { hasCachedToken } from "../knowledge/gcp-auth.js";
|
|
4
|
+
import { hasCachedToken, hasAdcCredentials } from "../knowledge/gcp-auth.js";
|
|
5
5
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
6
6
|
// Helpers
|
|
7
7
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -287,10 +287,12 @@ export function getActiveProfile(dir) {
|
|
|
287
287
|
? new Date(envCred.gcpTokenExpiresAt).getTime() < Date.now()
|
|
288
288
|
: false;
|
|
289
289
|
const hasLoginGcpToken = !!envCred?.gcpToken && !gcpTokenExpired;
|
|
290
|
+
// ADC file check (primary method — auto-refreshable, no gcloud needed at runtime)
|
|
291
|
+
const hasAdc = hasAdcCredentials();
|
|
290
292
|
// Check if a prior search already resolved a GCP token (any source).
|
|
291
293
|
// Note: gcloud availability is checked at search time, not here (avoids blocking execSync).
|
|
292
294
|
const hasResolvedAuth = hasCachedToken();
|
|
293
|
-
const searchAvailable = hasGcpToken || hasGcpSaKey || hasDeApiKey || hasLoginGcpToken || hasResolvedAuth;
|
|
295
|
+
const searchAvailable = hasGcpToken || hasAdc || hasGcpSaKey || hasDeApiKey || hasLoginGcpToken || hasResolvedAuth;
|
|
294
296
|
return {
|
|
295
297
|
name: config.current_profile,
|
|
296
298
|
display: displayName(profile.tenant.name, profile.environment.name, config),
|
|
@@ -34,6 +34,8 @@ const CATEGORY_STAGE_MAP = {
|
|
|
34
34
|
it_security: ["act", "respond"],
|
|
35
35
|
hr: ["act", "respond"],
|
|
36
36
|
analytics: ["analyze", "respond"],
|
|
37
|
+
utilities: ["convert", "format", "merge"],
|
|
38
|
+
agent_assist: ["respond", "classify", "summarize"],
|
|
37
39
|
};
|
|
38
40
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
39
41
|
// Manual Overrides — semantic constraints not derivable from category alone
|
|
@@ -238,6 +238,11 @@ export const TOOL_GUIDANCE = {
|
|
|
238
238
|
description: "Aggregate across ALL clients from cloud storage. Requires EMA_INTERNAL=1 and @ema.co gcloud auth.",
|
|
239
239
|
example: 'feedback(method="analyze", scope="global")',
|
|
240
240
|
},
|
|
241
|
+
{
|
|
242
|
+
name: "Read local messages",
|
|
243
|
+
description: "Read scoped messages from this session's in-memory buffer. For intra-session task coordination.",
|
|
244
|
+
example: 'feedback(method="local", scope="role:compliance")',
|
|
245
|
+
},
|
|
241
246
|
{
|
|
242
247
|
name: "Flush feedback",
|
|
243
248
|
description: "Send locally accumulated feedback to cloud storage. Happens automatically, but can be triggered manually.",
|
|
@@ -247,6 +252,7 @@ export const TOOL_GUIDANCE = {
|
|
|
247
252
|
nextSteps: {
|
|
248
253
|
submit: "Continue with your task. Your feedback will be reviewed to improve the toolkit.",
|
|
249
254
|
list: "Review entries and use analyze for aggregated insights.",
|
|
255
|
+
local: "Read messages scoped to you. Act on tasks, approvals, or coordination messages from other agents in this session.",
|
|
250
256
|
analyze: "Address actionable_items by updating guidance, resources, or patterns documentation. Global analysis (scope='global') uses BigQuery for cross-client aggregation. Pass days=7 to narrow the analysis window. Response includes _backend indicator, trends (week-over-week with direction), and version_comparison (error rates by version for regression detection).",
|
|
251
257
|
flush: "Feedback has been sent to cloud storage. Use analyze(scope='global') to see aggregated insights across all clients.",
|
|
252
258
|
},
|
|
@@ -4,14 +4,20 @@
|
|
|
4
4
|
* Resolution order (sync — getGcpAccessToken):
|
|
5
5
|
* 1. EMA_GCP_ACCESS_TOKEN env var (explicit token)
|
|
6
6
|
* 2. Cached token (from prior resolution)
|
|
7
|
-
* 3.
|
|
8
|
-
* 4.
|
|
9
|
-
* 5.
|
|
7
|
+
* 3. Application Default Credentials (~/.config/gcloud/application_default_credentials.json)
|
|
8
|
+
* 4. Stored OAuth credential from login flow (~/.ema-mcp/credentials.json)
|
|
9
|
+
* 5. GOOGLE_APPLICATION_CREDENTIALS / EMA_GCP_SA_KEY (service account key → JWT → token)
|
|
10
|
+
* 6. gcloud auth print-access-token (CLI fallback)
|
|
10
11
|
*
|
|
11
12
|
* Async variant (getGcpAccessTokenAsync) adds:
|
|
12
|
-
*
|
|
13
|
+
* 7. Interactive: auto-launch Google auth if TTY available (once per session)
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
+
* ADC auth (option 3, PRIMARY) uses the OAuth2 refresh_token grant:
|
|
16
|
+
* ADC file → refresh_token → POST oauth2.googleapis.com/token → access_token
|
|
17
|
+
* Auto-refreshes without user interaction. Set up once with:
|
|
18
|
+
* gcloud auth application-default login
|
|
19
|
+
*
|
|
20
|
+
* Service account auth (option 5) uses the standard Google OAuth2 JWT flow:
|
|
15
21
|
* SA key → sign JWT → POST oauth2.googleapis.com/token → access_token
|
|
16
22
|
* No external dependencies needed (uses Node.js built-in crypto).
|
|
17
23
|
*/
|
|
@@ -127,6 +133,77 @@ async function getTokenFromServiceAccount(sa) {
|
|
|
127
133
|
return undefined;
|
|
128
134
|
}
|
|
129
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Load ADC from the well-known file location.
|
|
138
|
+
* Written by `gcloud auth application-default login`.
|
|
139
|
+
*
|
|
140
|
+
* Only returns `authorized_user` type credentials (with refresh_token).
|
|
141
|
+
* Service account type is already handled by loadServiceAccountKey().
|
|
142
|
+
*/
|
|
143
|
+
function loadAdcCredentials() {
|
|
144
|
+
const debug = !!process.env.EMA_DEBUG;
|
|
145
|
+
try {
|
|
146
|
+
// Well-known path: ~/.config/gcloud/application_default_credentials.json
|
|
147
|
+
// On Windows: %APPDATA%\gcloud\application_default_credentials.json
|
|
148
|
+
const adcPath = process.platform === "win32"
|
|
149
|
+
? join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), "gcloud", "application_default_credentials.json")
|
|
150
|
+
: join(homedir(), ".config", "gcloud", "application_default_credentials.json");
|
|
151
|
+
const raw = readFileSync(adcPath, "utf-8");
|
|
152
|
+
const creds = JSON.parse(raw);
|
|
153
|
+
if (creds.type !== "authorized_user") {
|
|
154
|
+
if (debug)
|
|
155
|
+
console.error(`[GCP-AUTH] ADC file is type "${creds.type}", not authorized_user — skipping.`);
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
if (!creds.client_id || !creds.client_secret || !creds.refresh_token) {
|
|
159
|
+
if (debug)
|
|
160
|
+
console.error("[GCP-AUTH] ADC file missing required fields (client_id, client_secret, refresh_token).");
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
if (debug)
|
|
164
|
+
console.error("[GCP-AUTH] Found ADC authorized_user credentials.");
|
|
165
|
+
return creds;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// File doesn't exist or isn't readable — that's fine
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Exchange an ADC refresh token for a fresh access token.
|
|
174
|
+
* Uses the standard OAuth2 refresh_token grant.
|
|
175
|
+
*/
|
|
176
|
+
async function getTokenFromAdc(adc) {
|
|
177
|
+
const debug = !!process.env.EMA_DEBUG;
|
|
178
|
+
try {
|
|
179
|
+
const resp = await fetch("https://oauth2.googleapis.com/token", {
|
|
180
|
+
method: "POST",
|
|
181
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
182
|
+
body: new URLSearchParams({
|
|
183
|
+
client_id: adc.client_id,
|
|
184
|
+
client_secret: adc.client_secret,
|
|
185
|
+
refresh_token: adc.refresh_token,
|
|
186
|
+
grant_type: "refresh_token",
|
|
187
|
+
}).toString(),
|
|
188
|
+
signal: AbortSignal.timeout(10_000),
|
|
189
|
+
});
|
|
190
|
+
if (!resp.ok) {
|
|
191
|
+
const detail = await resp.text().catch(() => "");
|
|
192
|
+
if (debug)
|
|
193
|
+
console.error(`[GCP-AUTH] ADC token refresh failed: ${resp.status} — ${detail.slice(0, 200)}`);
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
const data = (await resp.json());
|
|
197
|
+
if (debug)
|
|
198
|
+
console.error(`[GCP-AUTH] ADC token refreshed (expires in ${data.expires_in ?? "?"}s).`);
|
|
199
|
+
return data.access_token;
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
if (debug)
|
|
203
|
+
console.error(`[GCP-AUTH] ADC token refresh error: ${err instanceof Error ? err.message : String(err)}`);
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
130
207
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
131
208
|
// Main token getter
|
|
132
209
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -136,9 +213,10 @@ async function getTokenFromServiceAccount(sa) {
|
|
|
136
213
|
* Resolution order:
|
|
137
214
|
* 1. EMA_GCP_ACCESS_TOKEN env var — skipped if previously rejected (401/403)
|
|
138
215
|
* 2. Cached token (if not expired)
|
|
139
|
-
* 3.
|
|
140
|
-
* 4.
|
|
141
|
-
* 5.
|
|
216
|
+
* 3. ADC file (~/.config/gcloud/application_default_credentials.json) — primary
|
|
217
|
+
* 4. Stored OAuth credential from login flow (~/.ema-mcp/credentials.json)
|
|
218
|
+
* 5. Service account key (GOOGLE_APPLICATION_CREDENTIALS or EMA_GCP_SA_KEY)
|
|
219
|
+
* 6. gcloud auth print-access-token (CLI fallback)
|
|
142
220
|
*
|
|
143
221
|
* Returns undefined if no token source is available.
|
|
144
222
|
*/
|
|
@@ -157,13 +235,28 @@ export function getGcpAccessToken() {
|
|
|
157
235
|
console.error(`[GCP-AUTH] Using cached token (expires in ${Math.round((_token.expiresAt - Date.now()) / 60_000)}m).`);
|
|
158
236
|
return _token.value;
|
|
159
237
|
}
|
|
160
|
-
// 3.
|
|
238
|
+
// 3. ADC file → async refresh token exchange (PRIMARY)
|
|
239
|
+
// Can't do async in a sync function, so fire-and-forget + cache for next call
|
|
240
|
+
const adc = loadAdcCredentials();
|
|
241
|
+
if (adc) {
|
|
242
|
+
if (debug)
|
|
243
|
+
console.error("[GCP-AUTH] Found ADC credentials — initiating async token refresh.");
|
|
244
|
+
getTokenFromAdc(adc).then((token) => {
|
|
245
|
+
if (token) {
|
|
246
|
+
_token = { value: token, expiresAt: Date.now() + TOKEN_TTL_MS };
|
|
247
|
+
}
|
|
248
|
+
}).catch(() => { });
|
|
249
|
+
// If we have a cached token from a prior ADC refresh, use it
|
|
250
|
+
if (_token && _token.value)
|
|
251
|
+
return _token.value;
|
|
252
|
+
}
|
|
253
|
+
// 4. Stored OAuth credential from login flow
|
|
161
254
|
const storedToken = loadStoredGcpToken();
|
|
162
255
|
if (storedToken) {
|
|
163
256
|
_token = { value: storedToken, expiresAt: Date.now() + TOKEN_TTL_MS };
|
|
164
257
|
return storedToken;
|
|
165
258
|
}
|
|
166
|
-
//
|
|
259
|
+
// 5. Service account key → async token exchange
|
|
167
260
|
// Can't do async in a sync function, so we kick off the exchange and cache for next call
|
|
168
261
|
const sa = loadServiceAccountKey();
|
|
169
262
|
if (sa) {
|
|
@@ -179,7 +272,7 @@ export function getGcpAccessToken() {
|
|
|
179
272
|
if (_token && _token.value)
|
|
180
273
|
return _token.value;
|
|
181
274
|
}
|
|
182
|
-
//
|
|
275
|
+
// 6. gcloud CLI
|
|
183
276
|
try {
|
|
184
277
|
if (debug)
|
|
185
278
|
console.error("[GCP-AUTH] Trying gcloud auth print-access-token...");
|
|
@@ -206,11 +299,11 @@ export function getGcpAccessToken() {
|
|
|
206
299
|
}
|
|
207
300
|
}
|
|
208
301
|
if (debug)
|
|
209
|
-
console.error("[GCP-AUTH] All
|
|
302
|
+
console.error("[GCP-AUTH] All 6 resolution steps failed. No GCP token available.");
|
|
210
303
|
return undefined;
|
|
211
304
|
}
|
|
212
305
|
/**
|
|
213
|
-
* Async version — waits for
|
|
306
|
+
* Async version — waits for ADC/SA token exchange.
|
|
214
307
|
* Use this when you can await (e.g., in search handlers).
|
|
215
308
|
*/
|
|
216
309
|
export async function getGcpAccessTokenAsync() {
|
|
@@ -221,13 +314,22 @@ export async function getGcpAccessTokenAsync() {
|
|
|
221
314
|
// 2. Cached token
|
|
222
315
|
if (_token && Date.now() < _token.expiresAt)
|
|
223
316
|
return _token.value;
|
|
224
|
-
// 3.
|
|
317
|
+
// 3. ADC file → await refresh token exchange (PRIMARY)
|
|
318
|
+
const adc = loadAdcCredentials();
|
|
319
|
+
if (adc) {
|
|
320
|
+
const token = await getTokenFromAdc(adc);
|
|
321
|
+
if (token) {
|
|
322
|
+
_token = { value: token, expiresAt: Date.now() + TOKEN_TTL_MS };
|
|
323
|
+
return token;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// 4. Stored OAuth credential from login flow
|
|
225
327
|
const storedToken = loadStoredGcpToken();
|
|
226
328
|
if (storedToken) {
|
|
227
329
|
_token = { value: storedToken, expiresAt: Date.now() + TOKEN_TTL_MS };
|
|
228
330
|
return storedToken;
|
|
229
331
|
}
|
|
230
|
-
//
|
|
332
|
+
// 5. Service account key → await token exchange
|
|
231
333
|
const sa = loadServiceAccountKey();
|
|
232
334
|
if (sa) {
|
|
233
335
|
const token = await getTokenFromServiceAccount(sa);
|
|
@@ -236,11 +338,11 @@ export async function getGcpAccessTokenAsync() {
|
|
|
236
338
|
return token;
|
|
237
339
|
}
|
|
238
340
|
}
|
|
239
|
-
//
|
|
341
|
+
// 6. gcloud CLI (sync — handled inside getGcpAccessToken)
|
|
240
342
|
const syncToken = getGcpAccessToken();
|
|
241
343
|
if (syncToken)
|
|
242
344
|
return syncToken;
|
|
243
|
-
//
|
|
345
|
+
// 7. Interactive: auto-launch Google auth if TTY available
|
|
244
346
|
const refreshed = await refreshGcpToken();
|
|
245
347
|
if (refreshed)
|
|
246
348
|
return refreshed;
|
|
@@ -374,6 +476,10 @@ export function getLastGcpAuthError() {
|
|
|
374
476
|
export function hasCachedToken() {
|
|
375
477
|
return !!_token && Date.now() < _token.expiresAt;
|
|
376
478
|
}
|
|
479
|
+
/** Whether ADC file exists with authorized_user credentials (fast sync check, no network). */
|
|
480
|
+
export function hasAdcCredentials() {
|
|
481
|
+
return !!loadAdcCredentials();
|
|
482
|
+
}
|
|
377
483
|
let _gcloudAvailable;
|
|
378
484
|
/**
|
|
379
485
|
* Whether `gcloud` CLI is installed (cached after first check).
|
|
@@ -27,6 +27,8 @@ export class GuidanceCache {
|
|
|
27
27
|
toolGuidanceMap = new Map();
|
|
28
28
|
tips = [];
|
|
29
29
|
topics = [];
|
|
30
|
+
patterns = [];
|
|
31
|
+
questions = [];
|
|
30
32
|
warmedAt = null;
|
|
31
33
|
constructor(searchFn, config) {
|
|
32
34
|
this.searchFn = searchFn;
|
|
@@ -102,12 +104,24 @@ export class GuidanceCache {
|
|
|
102
104
|
this.maybeRefresh();
|
|
103
105
|
return this.topics;
|
|
104
106
|
}
|
|
107
|
+
/** All workflow patterns. */
|
|
108
|
+
getPatterns() {
|
|
109
|
+
this.maybeRefresh();
|
|
110
|
+
return this.patterns;
|
|
111
|
+
}
|
|
112
|
+
/** All qualifying questions. */
|
|
113
|
+
getQuestions() {
|
|
114
|
+
this.maybeRefresh();
|
|
115
|
+
return this.questions;
|
|
116
|
+
}
|
|
105
117
|
// ── Internals ───────────────────────────────────────────────────────────
|
|
106
118
|
clear() {
|
|
107
119
|
this.rules = [];
|
|
108
120
|
this.toolGuidanceMap = new Map();
|
|
109
121
|
this.tips = [];
|
|
110
122
|
this.topics = [];
|
|
123
|
+
this.patterns = [];
|
|
124
|
+
this.questions = [];
|
|
111
125
|
}
|
|
112
126
|
/**
|
|
113
127
|
* Index artifacts by reading structured data from structData.data.
|
|
@@ -152,6 +166,12 @@ export class GuidanceCache {
|
|
|
152
166
|
case "guidance-topic":
|
|
153
167
|
this.topics.push(data);
|
|
154
168
|
break;
|
|
169
|
+
case "workflow-pattern":
|
|
170
|
+
this.patterns.push(data);
|
|
171
|
+
break;
|
|
172
|
+
case "qualifying-question":
|
|
173
|
+
this.questions.push(data);
|
|
174
|
+
break;
|
|
155
175
|
default:
|
|
156
176
|
break;
|
|
157
177
|
}
|
|
@@ -171,6 +191,10 @@ export class GuidanceCache {
|
|
|
171
191
|
return "contextual-tip";
|
|
172
192
|
if (tags.includes("guidance-topic"))
|
|
173
193
|
return "guidance-topic";
|
|
194
|
+
if (tags.includes("workflow-pattern"))
|
|
195
|
+
return "workflow-pattern";
|
|
196
|
+
if (tags.includes("qualifying-question"))
|
|
197
|
+
return "qualifying-question";
|
|
174
198
|
if (name.includes("tool-guidance"))
|
|
175
199
|
return "tool-guidance";
|
|
176
200
|
return undefined;
|