@ezmodo/mcp-server 0.13.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/README.md +305 -0
- package/config/development.js +20 -0
- package/config/endpoint-map.js +351 -0
- package/config/index.js +34 -0
- package/config/production.js +18 -0
- package/config/staging.js +18 -0
- package/handlers/access.js +141 -0
- package/handlers/activity.js +112 -0
- package/handlers/agents.js +95 -0
- package/handlers/ai-intelligence.js +55 -0
- package/handlers/attachments.js +30 -0
- package/handlers/catalogs.js +169 -0
- package/handlers/components.js +282 -0
- package/handlers/context-manifest.js +1150 -0
- package/handlers/decisions.js +114 -0
- package/handlers/designs.js +118 -0
- package/handlers/documents.js +227 -0
- package/handlers/entities.js +95 -0
- package/handlers/epics.js +190 -0
- package/handlers/facts.js +62 -0
- package/handlers/feature-flags.js +142 -0
- package/handlers/features.js +137 -0
- package/handlers/folders.js +127 -0
- package/handlers/git-context.js +917 -0
- package/handlers/github.js +72 -0
- package/handlers/graph.js +23 -0
- package/handlers/index.js +205 -0
- package/handlers/links.js +156 -0
- package/handlers/milestones.js +131 -0
- package/handlers/organizations.js +14 -0
- package/handlers/projects.js +122 -0
- package/handlers/recurring-tasks.js +33 -0
- package/handlers/tags.js +124 -0
- package/handlers/tasks.js +561 -0
- package/handlers/testing.js +116 -0
- package/handlers/todos.js +43 -0
- package/handlers/watchers.js +54 -0
- package/handlers/work-templates.js +32 -0
- package/index.js +175 -0
- package/lib/active-session.js +86 -0
- package/lib/auto-assign.js +93 -0
- package/lib/autolink.js +176 -0
- package/lib/changed-files.js +22 -0
- package/lib/env.js +45 -0
- package/lib/git-helpers.js +553 -0
- package/lib/git-utils.js +73 -0
- package/lib/http-client.js +164 -0
- package/lib/links-at-create.js +94 -0
- package/lib/local-cache.js +140 -0
- package/lib/logger.js +109 -0
- package/lib/manifest-loader.js +182 -0
- package/lib/manifest-query.js +686 -0
- package/lib/repo-config-dir.js +118 -0
- package/lib/version.js +10 -0
- package/lib/web-url.js +69 -0
- package/lib/worktree-tools.js +950 -0
- package/package.json +62 -0
- package/prompts/ai-workflow-automation.js +96 -0
- package/prompts/index.js +39 -0
- package/prompts/zephly-usage-guide-content.txt +631 -0
- package/prompts/zephly-usage-guide.js +119 -0
- package/tools/access-entity-types.js +28 -0
- package/tools/access.js +152 -0
- package/tools/activity.js +38 -0
- package/tools/agents.js +208 -0
- package/tools/ai-intelligence.js +111 -0
- package/tools/attachments.js +92 -0
- package/tools/catalogs.js +341 -0
- package/tools/components.js +249 -0
- package/tools/context-manifest.js +236 -0
- package/tools/decisions.js +168 -0
- package/tools/designs.js +222 -0
- package/tools/documents.js +287 -0
- package/tools/entities.js +223 -0
- package/tools/epics.js +267 -0
- package/tools/facts.js +70 -0
- package/tools/feature-flags.js +300 -0
- package/tools/features.js +246 -0
- package/tools/folders.js +122 -0
- package/tools/git-context.js +109 -0
- package/tools/github.js +172 -0
- package/tools/graph.js +70 -0
- package/tools/index.js +77 -0
- package/tools/link-params.js +93 -0
- package/tools/linkable-types.js +36 -0
- package/tools/links.js +199 -0
- package/tools/milestones.js +176 -0
- package/tools/organizations.js +23 -0
- package/tools/projects.js +172 -0
- package/tools/recurring-tasks.js +115 -0
- package/tools/tags.js +219 -0
- package/tools/task-item-schema.js +57 -0
- package/tools/task-type.js +33 -0
- package/tools/tasks.js +680 -0
- package/tools/testing.js +344 -0
- package/tools/todos.js +69 -0
- package/tools/watchers.js +81 -0
- package/tools/work-templates.js +96 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Endpoint Mapping: Cloud Functions → Go API
|
|
3
|
+
* Maps Cloud Functions endpoint names to Go API routes and HTTP methods
|
|
4
|
+
*
|
|
5
|
+
* Unified Hierarchy (Phase 6 - Simplified Data Model):
|
|
6
|
+
* - Projects - long-lived containers (primary)
|
|
7
|
+
* - Epics - related tasks grouped (project-scoped milestones)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const ENDPOINT_MAP = {
|
|
11
|
+
// Organizations
|
|
12
|
+
'mcpListOrganizations': { route: 'mcp/v1/organizations', method: 'GET' },
|
|
13
|
+
'mcpGetDefaultOrganization': { route: 'mcp/v1/organizations/default', method: 'GET' },
|
|
14
|
+
|
|
15
|
+
// Projects (primary)
|
|
16
|
+
'mcpListProjects': { route: 'mcp/v1/projects', method: 'GET' },
|
|
17
|
+
'mcpSearchProjects': { route: 'mcp/v1/projects/search', method: 'POST' },
|
|
18
|
+
'mcpGetProjectContext': { route: 'mcp/v1/projects/context', method: 'GET' },
|
|
19
|
+
'mcpGetProjectStory': { route: 'mcp/v1/projects/story', method: 'POST' },
|
|
20
|
+
'mcpCreateProject': { route: 'mcp/v1/projects', method: 'POST' },
|
|
21
|
+
'mcpUpdateProject': { route: 'mcp/v1/projects', method: 'PUT' },
|
|
22
|
+
'mcpGenerateProjectHowItWorks': { route: 'mcp/v1/projects/generate-how-it-works', method: 'POST' },
|
|
23
|
+
'mcpApplyProjectHowItWorks': { route: 'mcp/v1/projects/apply-how-it-works', method: 'POST' },
|
|
24
|
+
|
|
25
|
+
// Epics (project-scoped milestones)
|
|
26
|
+
'mcpCreateEpic': { route: 'mcp/v1/epics', method: 'POST' },
|
|
27
|
+
'mcpCreateEpicWithTasks': { route: 'mcp/v1/epics/with-tasks', method: 'POST' },
|
|
28
|
+
'mcpUpdateEpic': { route: 'mcp/v1/epics', method: 'PUT' },
|
|
29
|
+
'mcpSearchEpics': { route: 'mcp/v1/epics/search', method: 'POST' },
|
|
30
|
+
'mcpListEpics': { route: 'mcp/v1/epics', method: 'GET' },
|
|
31
|
+
'mcpGetEpic': { route: 'mcp/v1/epics/by-id', method: 'GET' },
|
|
32
|
+
// E-237 #2382: the epic is the fifth consumer of the grounding engine.
|
|
33
|
+
'mcpGenerateEpicHowItWorks': { route: 'mcp/v1/epics/generate-how-it-works', method: 'POST' },
|
|
34
|
+
'mcpApplyEpicHowItWorks': { route: 'mcp/v1/epics/apply-how-it-works', method: 'POST' },
|
|
35
|
+
|
|
36
|
+
// Tasks
|
|
37
|
+
'mcpCreateTask': { route: 'mcp/v1/tasks', method: 'POST' },
|
|
38
|
+
'mcpBulkCreateTasks': { route: 'mcp/v1/tasks/bulk', method: 'POST' },
|
|
39
|
+
'mcpUpdateTask': { route: 'mcp/v1/tasks', method: 'PUT' },
|
|
40
|
+
'mcpCompleteTask': { route: 'mcp/v1/tasks/complete', method: 'POST' },
|
|
41
|
+
'mcpDeferTask': { route: 'mcp/v1/tasks/defer', method: 'POST' },
|
|
42
|
+
'mcpSearchTasks': { route: 'mcp/v1/tasks/search', method: 'POST' },
|
|
43
|
+
'mcpSemanticTaskSearch': { route: 'mcp/v1/tasks/semantic-search', method: 'POST' },
|
|
44
|
+
'mcpGetTask': { route: 'mcp/v1/tasks/by-id', method: 'GET' },
|
|
45
|
+
'mcpAddTaskContext': { route: 'mcp/v1/tasks/context', method: 'POST' },
|
|
46
|
+
'mcpLinkCommitToTask': { route: 'mcp/v1/tasks/link-commit', method: 'POST' },
|
|
47
|
+
'mcpUnlinkCommitFromTask': { route: 'mcp/v1/tasks/unlink-commit', method: 'POST' },
|
|
48
|
+
'mcpGetTaskCommits': { route: 'mcp/v1/tasks/commits', method: 'GET' },
|
|
49
|
+
'mcpGenerateTaskHowItWorks': { route: 'mcp/v1/tasks/generate-how-it-works', method: 'POST' },
|
|
50
|
+
'mcpApplyTaskHowItWorks': { route: 'mcp/v1/tasks/apply-how-it-works', method: 'POST' },
|
|
51
|
+
|
|
52
|
+
// Watching / subscriptions and the notification inbox (E-41)
|
|
53
|
+
'mcpManageWatch': { route: 'mcp/v1/watch', method: 'POST' },
|
|
54
|
+
'mcpListWatched': { route: 'mcp/v1/watching', method: 'GET' },
|
|
55
|
+
'mcpListNotifications': { route: 'mcp/v1/notifications', method: 'GET' },
|
|
56
|
+
|
|
57
|
+
// Documentation
|
|
58
|
+
'mcpGetDocumentation': { route: 'mcp/v1/documents', method: 'GET' },
|
|
59
|
+
'mcpGetDocument': { route: 'mcp/v1/documents/by-id', method: 'GET' },
|
|
60
|
+
'mcpCreateDocument': { route: 'mcp/v1/documents', method: 'POST' },
|
|
61
|
+
'mcpUpdateDocument': { route: 'mcp/v1/documents', method: 'PUT' },
|
|
62
|
+
// Org-scoped (project-less) document listing (E-195)
|
|
63
|
+
'mcpListOrgDocuments': { route: 'mcp/v1/org-documents', method: 'GET' },
|
|
64
|
+
|
|
65
|
+
// Document Versions (list/get only — create not yet implemented in Go API)
|
|
66
|
+
'mcpListDocumentVersions': { route: 'mcp/v1/document-versions', method: 'GET' },
|
|
67
|
+
'mcpGetDocumentVersion': { route: 'mcp/v1/document-versions/by-number', method: 'GET' },
|
|
68
|
+
|
|
69
|
+
// Document Templates
|
|
70
|
+
'mcpListDocumentTemplates': { route: 'mcp/v1/document-templates', method: 'GET' },
|
|
71
|
+
'mcpGetDocumentTemplate': { route: 'mcp/v1/document-templates/by-id', method: 'GET' },
|
|
72
|
+
'mcpCreateDocumentTemplate': { route: 'mcp/v1/document-templates', method: 'POST' },
|
|
73
|
+
'mcpDeleteDocumentTemplate': { route: 'mcp/v1/document-templates', method: 'DELETE' },
|
|
74
|
+
|
|
75
|
+
// Attachments (read/manage only — E-21 #158)
|
|
76
|
+
'mcpListAttachments': { route: 'mcp/v1/attachments', method: 'GET' },
|
|
77
|
+
'mcpGetAttachmentUrl': { route: 'mcp/v1/attachments/download-url', method: 'POST' },
|
|
78
|
+
'mcpDeleteAttachment': { route: 'mcp/v1/attachments', method: 'DELETE' },
|
|
79
|
+
|
|
80
|
+
// Document Folders
|
|
81
|
+
'mcpListFolders': { route: 'mcp/v1/folders', method: 'GET' },
|
|
82
|
+
'mcpGetFolderTree': { route: 'mcp/v1/folders/tree', method: 'GET' },
|
|
83
|
+
'mcpCreateFolder': { route: 'mcp/v1/folders', method: 'POST' },
|
|
84
|
+
'mcpUpdateFolder': { route: 'mcp/v1/folders', method: 'PUT' },
|
|
85
|
+
'mcpMoveFolder': { route: 'mcp/v1/folders/move', method: 'POST' },
|
|
86
|
+
'mcpDeleteFolder': { route: 'mcp/v1/folders', method: 'DELETE' },
|
|
87
|
+
// Org reserved areas (Goals, Features, ADRs, How it works) — ensures + lists (E-195)
|
|
88
|
+
'mcpGetOrgAreas': { route: 'mcp/v1/org-areas', method: 'GET' },
|
|
89
|
+
|
|
90
|
+
// Goals
|
|
91
|
+
'mcpCreateGoal': { route: 'mcp/v1/goals', method: 'POST' },
|
|
92
|
+
'mcpGetGoal': { route: 'mcp/v1/goals/by-id', method: 'GET' },
|
|
93
|
+
'mcpUpdateGoal': { route: 'mcp/v1/goals', method: 'PUT' },
|
|
94
|
+
'mcpListGoals': { route: 'mcp/v1/goals', method: 'GET' },
|
|
95
|
+
'mcpDeleteGoal': { route: 'mcp/v1/goals', method: 'DELETE' },
|
|
96
|
+
'mcpGenerateGoalHowItWorks': { route: 'mcp/v1/goals/generate-how-it-works', method: 'POST' },
|
|
97
|
+
'mcpApplyGoalHowItWorks': { route: 'mcp/v1/goals/apply-how-it-works', method: 'POST' },
|
|
98
|
+
|
|
99
|
+
// Tags (organization-scoped categorization)
|
|
100
|
+
'mcpCreateTag': { route: 'mcp/v1/tags', method: 'POST' },
|
|
101
|
+
'mcpListTags': { route: 'mcp/v1/tags', method: 'GET' },
|
|
102
|
+
'mcpGetTag': { route: 'mcp/v1/tags/by-id', method: 'GET' },
|
|
103
|
+
'mcpUpdateTag': { route: 'mcp/v1/tags', method: 'PUT' },
|
|
104
|
+
'mcpDeleteTag': { route: 'mcp/v1/tags', method: 'DELETE' },
|
|
105
|
+
'mcpMergeTags': { route: 'mcp/v1/tags/merge', method: 'POST' },
|
|
106
|
+
'mcpBulkTagEntities': { route: 'mcp/v1/tags/bulk', method: 'POST' },
|
|
107
|
+
'mcpFindEntitiesByTags': { route: 'mcp/v1/tags/find-entities', method: 'POST' },
|
|
108
|
+
'mcpSuggestTags': { route: 'mcp/v1/tags/suggest', method: 'POST' }, // AI-powered semantic tag suggestions
|
|
109
|
+
|
|
110
|
+
// Teams
|
|
111
|
+
'mcpCreateTeam': { route: 'mcp/v1/teams', method: 'POST' },
|
|
112
|
+
|
|
113
|
+
// Organization analysis
|
|
114
|
+
'mcpAnalyzeProjectOrganization': { route: 'mcp/v1/organization/analyze', method: 'POST' },
|
|
115
|
+
|
|
116
|
+
// AI Intelligence endpoints
|
|
117
|
+
'mcpGetProjectInsights': { route: 'mcp/v1/insights/project', method: 'POST' },
|
|
118
|
+
'mcpSuggestNextActions': { route: 'mcp/v1/insights/suggest-next', method: 'POST' },
|
|
119
|
+
'mcpAnalyzeDependencyGraph': { route: 'mcp/v1/insights/dependency-graph', method: 'POST' },
|
|
120
|
+
'mcpAnalyzeBuildFailure': { route: 'mcp/v1/insights/build-failure', method: 'POST' },
|
|
121
|
+
'mcpPredictDeploymentRisk': { route: 'mcp/v1/insights/deployment-risk', method: 'POST' },
|
|
122
|
+
|
|
123
|
+
'mcpEstimateTask': { route: 'mcp/v1/intelligence/estimate', method: 'GET' },
|
|
124
|
+
'mcpGetInferredDependencies': { route: 'mcp/v1/intelligence/inferred-dependencies', method: 'GET' },
|
|
125
|
+
|
|
126
|
+
// Entity access control (#2170). Reads take the optional read:access scope;
|
|
127
|
+
// the three mutations require the dedicated write:access scope server-side.
|
|
128
|
+
'mcpGetAccess': { route: 'mcp/v1/access', method: 'GET' },
|
|
129
|
+
'mcpUpdateAccess': { route: 'mcp/v1/access', method: 'PUT' },
|
|
130
|
+
'mcpAddAccessEntry': { route: 'mcp/v1/access/entries', method: 'POST' },
|
|
131
|
+
'mcpRemoveAccessEntry': { route: 'mcp/v1/access/entries', method: 'DELETE' },
|
|
132
|
+
'mcpCheckAccess': { route: 'mcp/v1/access/check', method: 'POST' },
|
|
133
|
+
|
|
134
|
+
// Components (project-scoped codebase areas)
|
|
135
|
+
'mcpCreateComponent': { route: 'mcp/v1/components', method: 'POST' },
|
|
136
|
+
'mcpListComponents': { route: 'mcp/v1/components', method: 'GET' },
|
|
137
|
+
'mcpUpdateComponent': { route: 'mcp/v1/components', method: 'PUT' },
|
|
138
|
+
'mcpDeleteComponent': { route: 'mcp/v1/components', method: 'DELETE' },
|
|
139
|
+
'mcpGetComponentStats': { route: 'mcp/v1/components/stats', method: 'GET' },
|
|
140
|
+
'mcpGetComponentDependencyGraph': { route: 'mcp/v1/components/dependency-graph', method: 'GET' },
|
|
141
|
+
// E-223: screen→screen navigation edges (the screen-flow map).
|
|
142
|
+
'mcpGetComponentNavigation': { route: 'mcp/v1/components/navigation', method: 'GET' },
|
|
143
|
+
'mcpAddComponentNavigation': { route: 'mcp/v1/components/navigation', method: 'POST' },
|
|
144
|
+
'mcpRemoveComponentNavigation': { route: 'mcp/v1/components/navigation', method: 'DELETE' },
|
|
145
|
+
// E-239 #2409: re-derive the flow map from code instead of hand-drawing it.
|
|
146
|
+
'mcpDeriveComponentNavigation': { route: 'mcp/v1/components/navigation/derive', method: 'POST' },
|
|
147
|
+
'mcpAddComponentDependency': { route: 'mcp/v1/components/dependencies', method: 'POST' },
|
|
148
|
+
'mcpRemoveComponentDependency': { route: 'mcp/v1/components/dependencies', method: 'DELETE' },
|
|
149
|
+
// E-168: unified UI inventory — Components span kinds area|screen|page|component.
|
|
150
|
+
// discover proposes page/component surfaces from the Context Manifest;
|
|
151
|
+
// import bulk-creates surfaces (optionally nested and/or feature-linked).
|
|
152
|
+
'mcpDiscoverComponents': { route: 'mcp/v1/components/discover', method: 'GET' },
|
|
153
|
+
'mcpImportComponents': { route: 'mcp/v1/components/import', method: 'POST' },
|
|
154
|
+
|
|
155
|
+
// Polymorphic links — single CRUD surface for any entity_links row
|
|
156
|
+
// (blocked_by, relates_to) across tasks, epics, projects, documents.
|
|
157
|
+
'mcpAddLink': { route: 'mcp/v1/links', method: 'POST' },
|
|
158
|
+
'mcpRemoveLink': { route: 'mcp/v1/links', method: 'DELETE' },
|
|
159
|
+
'mcpListLinks': { route: 'mcp/v1/links', method: 'GET' },
|
|
160
|
+
'mcpVerifyLink': { route: 'mcp/v1/links/verify', method: 'POST' },
|
|
161
|
+
// E-225 read-only autolink surfaces. POST rather than GET because both take a
|
|
162
|
+
// list of paths: a query string flattens an array to a comma-joined value,
|
|
163
|
+
// and file paths may legitimately contain commas.
|
|
164
|
+
'mcpResolvePaths': { route: 'mcp/v1/links/resolve-paths', method: 'POST' },
|
|
165
|
+
'mcpPreviewLinks': { route: 'mcp/v1/links/preview', method: 'POST' },
|
|
166
|
+
// No 'mcpLinkSuggestions' here on purpose: mcp/v1/links/suggestions reads the
|
|
167
|
+
// same agent_suggestions rows as mcpListAgentSuggestions below, which already
|
|
168
|
+
// has a tool. Two ways to read one queue is how they drift apart (#2297).
|
|
169
|
+
|
|
170
|
+
// E-150 background-agent control plane. Suggestions live in
|
|
171
|
+
// agent_suggestions; accept/reject mutate state via core/agents.Dispatcher.
|
|
172
|
+
'mcpListAgentSuggestions': { route: 'mcp/v1/agent-suggestions', method: 'GET' },
|
|
173
|
+
'mcpAcceptAgentSuggestion': { route: 'mcp/v1/agent-suggestions/accept', method: 'POST' },
|
|
174
|
+
'mcpRejectAgentSuggestion': { route: 'mcp/v1/agent-suggestions/reject', method: 'POST' },
|
|
175
|
+
'mcpRunAgentNow': { route: 'mcp/v1/agent-runs', method: 'POST' },
|
|
176
|
+
'mcpConfigureAgent': { route: 'mcp/v1/agent-settings', method: 'PUT' },
|
|
177
|
+
|
|
178
|
+
// Project facts — long-lived knowledge entries (decisions, conventions,
|
|
179
|
+
// reference info). The MCP handlers in handlers/facts.js were already
|
|
180
|
+
// calling these aliases; adding the route mappings here so they don't
|
|
181
|
+
// hit "Unknown endpoint".
|
|
182
|
+
'mcpCreateFact': { route: 'mcp/v1/facts', method: 'POST' },
|
|
183
|
+
'mcpListFacts': { route: 'mcp/v1/facts', method: 'GET' },
|
|
184
|
+
'mcpUpdateFact': { route: 'mcp/v1/facts', method: 'PUT' },
|
|
185
|
+
'mcpDeleteFact': { route: 'mcp/v1/facts', method: 'DELETE' },
|
|
186
|
+
|
|
187
|
+
// Milestones (version releases and initiatives)
|
|
188
|
+
'mcpCreateMilestone': { route: 'mcp/v1/milestones', method: 'POST' },
|
|
189
|
+
'mcpGetMilestone': { route: 'mcp/v1/milestones/by-id', method: 'GET' },
|
|
190
|
+
'mcpUpdateMilestone': { route: 'mcp/v1/milestones', method: 'PUT' },
|
|
191
|
+
'mcpListMilestones': { route: 'mcp/v1/milestones', method: 'GET' },
|
|
192
|
+
'mcpDeleteMilestone': { route: 'mcp/v1/milestones', method: 'DELETE' },
|
|
193
|
+
// Epic-to-milestone linking
|
|
194
|
+
'mcpLinkEpicToMilestone': { route: 'mcp/v1/milestones/link-epic', method: 'POST' },
|
|
195
|
+
'mcpUnlinkEpicFromMilestone': { route: 'mcp/v1/milestones/unlink-epic', method: 'POST' },
|
|
196
|
+
'mcpGenerateMilestoneChangelog': { route: 'mcp/v1/milestones/generate-changelog', method: 'POST' },
|
|
197
|
+
'mcpGetMilestoneProgress': { route: 'mcp/v1/milestones/progress', method: 'GET' },
|
|
198
|
+
'mcpReorderMilestoneEpics': { route: 'mcp/v1/milestones/reorder-epics', method: 'POST' },
|
|
199
|
+
// Suite-to-milestone linking
|
|
200
|
+
'mcpLinkSuiteToMilestone': { route: 'mcp/v1/milestones/link-suite', method: 'POST' },
|
|
201
|
+
'mcpUnlinkSuiteFromMilestone': { route: 'mcp/v1/milestones/unlink-suite', method: 'POST' },
|
|
202
|
+
|
|
203
|
+
// Features (Feature Compendium, E-162) — org-level product capabilities
|
|
204
|
+
'mcpCreateFeature': { route: 'mcp/v1/features', method: 'POST' },
|
|
205
|
+
'mcpGetFeature': { route: 'mcp/v1/features/by-id', method: 'GET' },
|
|
206
|
+
'mcpListFeatures': { route: 'mcp/v1/features', method: 'GET' },
|
|
207
|
+
'mcpUpdateFeature': { route: 'mcp/v1/features', method: 'PUT' },
|
|
208
|
+
'mcpDeleteFeature': { route: 'mcp/v1/features', method: 'DELETE' },
|
|
209
|
+
'mcpListFeatureLinks': { route: 'mcp/v1/features/links', method: 'GET' },
|
|
210
|
+
'mcpGetFeatureDetail': { route: 'mcp/v1/features/detail', method: 'GET' },
|
|
211
|
+
'mcpSearchFeatures': { route: 'mcp/v1/features/search', method: 'GET' },
|
|
212
|
+
'mcpLinkFeatureArtifact': { route: 'mcp/v1/features/link', method: 'POST' },
|
|
213
|
+
'mcpUnlinkFeatureArtifact': { route: 'mcp/v1/features/link', method: 'DELETE' },
|
|
214
|
+
'mcpPromoteEpicToFeature': { route: 'mcp/v1/features/promote-epic', method: 'POST' },
|
|
215
|
+
'mcpGenerateHowItWorks': { route: 'mcp/v1/features/generate-how-it-works', method: 'POST' },
|
|
216
|
+
'mcpApplyHowItWorks': { route: 'mcp/v1/features/apply-how-it-works', method: 'POST' },
|
|
217
|
+
'mcpApplyFeatureInit': { route: 'mcp/v1/features/init-apply', method: 'POST' },
|
|
218
|
+
|
|
219
|
+
// Decisions / ADRs (E-170) — org-level durable decision records that link to
|
|
220
|
+
// features and other artifacts via the generic link graph.
|
|
221
|
+
'mcpCreateDecision': { route: 'mcp/v1/decisions', method: 'POST' },
|
|
222
|
+
'mcpListDecisions': { route: 'mcp/v1/decisions', method: 'GET' },
|
|
223
|
+
'mcpGetDecision': { route: 'mcp/v1/decisions/by-id', method: 'GET' },
|
|
224
|
+
'mcpListDecisionLinks': { route: 'mcp/v1/decisions/links', method: 'GET' },
|
|
225
|
+
'mcpUpdateDecision': { route: 'mcp/v1/decisions', method: 'PUT' },
|
|
226
|
+
'mcpDeleteDecision': { route: 'mcp/v1/decisions', method: 'DELETE' },
|
|
227
|
+
'mcpSupersedeDecision': { route: 'mcp/v1/decisions/supersede', method: 'POST' },
|
|
228
|
+
'mcpPromoteDecisionFromKnowledge': { route: 'mcp/v1/decisions/promote-from-knowledge', method: 'POST' },
|
|
229
|
+
'mcpLinkDecisionArtifact': { route: 'mcp/v1/decisions/link', method: 'POST' },
|
|
230
|
+
'mcpUnlinkDecisionArtifact': { route: 'mcp/v1/decisions/link', method: 'DELETE' },
|
|
231
|
+
|
|
232
|
+
// Recurring task schedules (E-211) — "what task to create, on what cadence".
|
|
233
|
+
'mcpCreateRecurringTask': { route: 'mcp/v1/recurring-tasks', method: 'POST' },
|
|
234
|
+
'mcpListRecurringTasks': { route: 'mcp/v1/recurring-tasks', method: 'GET' },
|
|
235
|
+
'mcpGetRecurringTask': { route: 'mcp/v1/recurring-tasks/by-id', method: 'GET' },
|
|
236
|
+
'mcpUpdateRecurringTask': { route: 'mcp/v1/recurring-tasks', method: 'PUT' },
|
|
237
|
+
'mcpPauseRecurringTask': { route: 'mcp/v1/recurring-tasks/pause', method: 'POST' },
|
|
238
|
+
'mcpResumeRecurringTask': { route: 'mcp/v1/recurring-tasks/resume', method: 'POST' },
|
|
239
|
+
'mcpDeleteRecurringTask': { route: 'mcp/v1/recurring-tasks', method: 'DELETE' },
|
|
240
|
+
|
|
241
|
+
// Work templates (E-211) — reusable task/epic blueprints + instantiate.
|
|
242
|
+
'mcpCreateWorkTemplate': { route: 'mcp/v1/work-templates', method: 'POST' },
|
|
243
|
+
'mcpListWorkTemplates': { route: 'mcp/v1/work-templates', method: 'GET' },
|
|
244
|
+
'mcpGetWorkTemplate': { route: 'mcp/v1/work-templates/by-id', method: 'GET' },
|
|
245
|
+
'mcpUpdateWorkTemplate': { route: 'mcp/v1/work-templates', method: 'PUT' },
|
|
246
|
+
'mcpDeleteWorkTemplate': { route: 'mcp/v1/work-templates', method: 'DELETE' },
|
|
247
|
+
'mcpInstantiateWorkTemplate': { route: 'mcp/v1/work-templates/instantiate', method: 'POST' },
|
|
248
|
+
|
|
249
|
+
// Designs (the in-product living design system) — org-level AI-authored HTML/CSS
|
|
250
|
+
// artifacts (kind theme|component|page) that link to features and other artifacts.
|
|
251
|
+
'mcpCreateDesign': { route: 'mcp/v1/designs', method: 'POST' },
|
|
252
|
+
'mcpListDesigns': { route: 'mcp/v1/designs', method: 'GET' },
|
|
253
|
+
'mcpGetDesign': { route: 'mcp/v1/designs/by-id', method: 'GET' },
|
|
254
|
+
'mcpGetDesignSystem': { route: 'mcp/v1/designs/system', method: 'GET' },
|
|
255
|
+
'mcpListDesignLinks': { route: 'mcp/v1/designs/links', method: 'GET' },
|
|
256
|
+
'mcpUpdateDesign': { route: 'mcp/v1/designs', method: 'PUT' },
|
|
257
|
+
'mcpDeleteDesign': { route: 'mcp/v1/designs', method: 'DELETE' },
|
|
258
|
+
'mcpLinkDesign': { route: 'mcp/v1/designs/link', method: 'POST' },
|
|
259
|
+
'mcpUnlinkDesign': { route: 'mcp/v1/designs/link', method: 'DELETE' },
|
|
260
|
+
|
|
261
|
+
// Catalogs (E-215) — org-level generalized code-derived catalogs, each versioned
|
|
262
|
+
'mcpCreateCatalog': { route: 'mcp/v1/catalogs', method: 'POST' },
|
|
263
|
+
'mcpListCatalogs': { route: 'mcp/v1/catalogs', method: 'GET' },
|
|
264
|
+
'mcpGetCatalog': { route: 'mcp/v1/catalogs/by-id', method: 'GET' },
|
|
265
|
+
'mcpGetCurrentCatalog': { route: 'mcp/v1/catalogs/current', method: 'GET' },
|
|
266
|
+
'mcpListCatalogVersions': { route: 'mcp/v1/catalogs/versions', method: 'GET' },
|
|
267
|
+
'mcpGetCatalogVersion': { route: 'mcp/v1/catalogs/version', method: 'GET' },
|
|
268
|
+
'mcpDiffCatalog': { route: 'mcp/v1/catalogs/diff', method: 'GET' },
|
|
269
|
+
'mcpListCatalogLinks': { route: 'mcp/v1/catalogs/links', method: 'GET' },
|
|
270
|
+
'mcpUpdateCatalog': { route: 'mcp/v1/catalogs', method: 'PUT' },
|
|
271
|
+
'mcpDeleteCatalog': { route: 'mcp/v1/catalogs', method: 'DELETE' },
|
|
272
|
+
'mcpSnapshotCatalog': { route: 'mcp/v1/catalogs/snapshot', method: 'POST' },
|
|
273
|
+
'mcpLinkCatalog': { route: 'mcp/v1/catalogs/link', method: 'POST' },
|
|
274
|
+
'mcpUnlinkCatalog': { route: 'mcp/v1/catalogs/link', method: 'DELETE' },
|
|
275
|
+
// Item-level links (E-218) — attach work or an external URL to one catalog entry
|
|
276
|
+
'mcpListCatalogItems': { route: 'mcp/v1/catalogs/items', method: 'GET' },
|
|
277
|
+
'mcpLinkCatalogItem': { route: 'mcp/v1/catalogs/items/link', method: 'POST' },
|
|
278
|
+
'mcpUnlinkCatalogItem': { route: 'mcp/v1/catalogs/items/link', method: 'DELETE' },
|
|
279
|
+
|
|
280
|
+
// Feature Flags (E-149) — org-level PM-aware runtime gates that link to the
|
|
281
|
+
// feature(s) they gate. /evaluate runs the deterministic, prerequisite-aware
|
|
282
|
+
// evaluator through the in-process cache.
|
|
283
|
+
'mcpCreateFeatureFlag': { route: 'mcp/v1/feature-flags', method: 'POST' },
|
|
284
|
+
'mcpListFeatureFlags': { route: 'mcp/v1/feature-flags', method: 'GET' },
|
|
285
|
+
'mcpGetFeatureFlag': { route: 'mcp/v1/feature-flags/by-id', method: 'GET' },
|
|
286
|
+
'mcpListFeatureFlagLinks': { route: 'mcp/v1/feature-flags/links', method: 'GET' },
|
|
287
|
+
'mcpEvaluateFeatureFlag': { route: 'mcp/v1/feature-flags/evaluate', method: 'POST' },
|
|
288
|
+
'mcpUpdateFeatureFlag': { route: 'mcp/v1/feature-flags', method: 'PUT' },
|
|
289
|
+
'mcpDeleteFeatureFlag': { route: 'mcp/v1/feature-flags', method: 'DELETE' },
|
|
290
|
+
'mcpTransitionFeatureFlag': { route: 'mcp/v1/feature-flags/transition', method: 'POST' },
|
|
291
|
+
'mcpLinkFeatureFlag': { route: 'mcp/v1/feature-flags/link', method: 'POST' },
|
|
292
|
+
'mcpUnlinkFeatureFlag': { route: 'mcp/v1/feature-flags/link', method: 'DELETE' },
|
|
293
|
+
// Environment registry + per-env flag config (E-186).
|
|
294
|
+
'mcpListEnvironments': { route: 'mcp/v1/feature-flags/environments', method: 'GET' },
|
|
295
|
+
'mcpCreateEnvironment': { route: 'mcp/v1/feature-flags/environments', method: 'POST' },
|
|
296
|
+
'mcpUpdateEnvironment': { route: 'mcp/v1/feature-flags/environments', method: 'PUT' },
|
|
297
|
+
'mcpDeleteEnvironment': { route: 'mcp/v1/feature-flags/environments', method: 'DELETE' },
|
|
298
|
+
'mcpSetDefaultEnvironment': { route: 'mcp/v1/feature-flags/environments/default', method: 'POST' },
|
|
299
|
+
'mcpSetFlagEnvironmentConfig': { route: 'mcp/v1/feature-flags/flag-environment', method: 'PUT' },
|
|
300
|
+
|
|
301
|
+
// Activity Timeline
|
|
302
|
+
'mcpGetProjectChanges': { route: 'mcp/v1/projects/activity', method: 'GET' },
|
|
303
|
+
|
|
304
|
+
// Context Manifest (cloud-backed project context)
|
|
305
|
+
'mcpSearchManifest': { route: 'mcp/v1/manifest/search', method: 'POST' },
|
|
306
|
+
'mcpGetRelatedFiles': { route: 'mcp/v1/manifest/related', method: 'GET' },
|
|
307
|
+
'mcpGetManifestOverview': { route: 'mcp/v1/manifest/overview', method: 'GET' },
|
|
308
|
+
'mcpGetCriticalFiles': { route: 'mcp/v1/manifest/critical-files', method: 'GET' },
|
|
309
|
+
'mcpUpdateManifestEntries': { route: 'mcp/v1/manifest/update-entries', method: 'POST' },
|
|
310
|
+
'mcpRebuildManifest': { route: 'mcp/v1/manifest/rebuild', method: 'POST' },
|
|
311
|
+
|
|
312
|
+
// Knowledge Graph (graph query tools)
|
|
313
|
+
'mcpQueryProjectGraph': { route: 'mcp/v1/graph/query', method: 'POST' },
|
|
314
|
+
'mcpAnalyzeImpact': { route: 'mcp/v1/graph/impact', method: 'POST' },
|
|
315
|
+
'mcpGetGraphStats': { route: 'mcp/v1/graph/stats', method: 'GET' },
|
|
316
|
+
// Navigation-graph projection over entity_links (E-209) — agent traversal
|
|
317
|
+
'mcpTraverseGraph': { route: 'mcp/v1/graph/traverse', method: 'POST' },
|
|
318
|
+
|
|
319
|
+
// GitHub endpoints
|
|
320
|
+
'mcpCreatePullRequest': { route: 'mcp/v1/github/pull-request', method: 'POST' },
|
|
321
|
+
'mcpUpdatePullRequest': { route: 'mcp/v1/github/pull-request', method: 'PUT' },
|
|
322
|
+
'mcpMergePullRequest': { route: 'mcp/v1/github/pull-request/merge', method: 'POST' },
|
|
323
|
+
'mcpClosePullRequest': { route: 'mcp/v1/github/pull-request/close', method: 'POST' },
|
|
324
|
+
'mcpRequestPRReview': { route: 'mcp/v1/github/pull-request/review', method: 'POST' },
|
|
325
|
+
'mcpSuggestReviewers': { route: 'mcp/v1/github/pull-request/suggest-reviewers', method: 'POST' },
|
|
326
|
+
'mcpListRepositories': { route: 'mcp/v1/github/repositories', method: 'GET' },
|
|
327
|
+
|
|
328
|
+
// Todos (personal todo list)
|
|
329
|
+
'mcpCreateTodo': { route: 'mcp/v1/todos', method: 'POST' },
|
|
330
|
+
'mcpListTodos': { route: 'mcp/v1/todos', method: 'GET' },
|
|
331
|
+
'mcpCompleteTodo': { route: 'mcp/v1/todos/complete', method: 'POST' },
|
|
332
|
+
'mcpMoveTodo': { route: 'mcp/v1/todos/move', method: 'POST' },
|
|
333
|
+
|
|
334
|
+
// Testing (project test cases)
|
|
335
|
+
'mcpListTestCases': { route: 'mcp/v1/testing/cases', method: 'GET' },
|
|
336
|
+
'mcpGetTestCase': { route: 'mcp/v1/testing/case', method: 'GET' },
|
|
337
|
+
'mcpCreateTestCase': { route: 'mcp/v1/testing/cases', method: 'POST' },
|
|
338
|
+
'mcpUpdateTestCase': { route: 'mcp/v1/testing/cases', method: 'PUT' },
|
|
339
|
+
'mcpDeleteTestCase': { route: 'mcp/v1/testing/cases', method: 'DELETE' },
|
|
340
|
+
'mcpRecordTestRun': { route: 'mcp/v1/testing/runs', method: 'POST' },
|
|
341
|
+
'mcpGetTestingSummary': { route: 'mcp/v1/testing/summary', method: 'GET' },
|
|
342
|
+
|
|
343
|
+
// Test Suites (project-level test case grouping)
|
|
344
|
+
'mcpListTestSuites': { route: 'mcp/v1/testing/suites', method: 'GET' },
|
|
345
|
+
'mcpGetTestSuite': { route: 'mcp/v1/testing/suites/by-id', method: 'GET' },
|
|
346
|
+
'mcpCreateTestSuite': { route: 'mcp/v1/testing/suites', method: 'POST' },
|
|
347
|
+
'mcpUpdateTestSuite': { route: 'mcp/v1/testing/suites', method: 'PUT' },
|
|
348
|
+
'mcpDeleteTestSuite': { route: 'mcp/v1/testing/suites', method: 'DELETE' },
|
|
349
|
+
'mcpAddCasesToSuite': { route: 'mcp/v1/testing/suites/add-cases', method: 'POST' },
|
|
350
|
+
'mcpRemoveCasesFromSuite': { route: 'mcp/v1/testing/suites/remove-cases', method: 'POST' },
|
|
351
|
+
};
|
package/config/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Configuration Loader
|
|
3
|
+
*
|
|
4
|
+
* Imports the appropriate config based on BUILD_ENV at build time.
|
|
5
|
+
* This file is used during the build process to bake in the environment.
|
|
6
|
+
*
|
|
7
|
+
* BUILD_ENV should be set when building:
|
|
8
|
+
* - BUILD_ENV=production (for production builds - the default)
|
|
9
|
+
* - BUILD_ENV=staging (for staging builds)
|
|
10
|
+
* - BUILD_ENV=development (for local dev)
|
|
11
|
+
*
|
|
12
|
+
* The default is PRODUCTION, not development. Every in-repo consumer sets
|
|
13
|
+
* BUILD_ENV explicitly -- `npm run dev`, the test scripts, and the CLI's
|
|
14
|
+
* embedded server (cli/src/lib/mcp-server.ts assigns process.env.BUILD_ENV
|
|
15
|
+
* from BUILD_ENVIRONMENT before importing a handler). So the ONLY caller that
|
|
16
|
+
* arrives here with it unset is an installed copy: `npx @ezmodo/mcp-server`,
|
|
17
|
+
* or the Claude Code plugin that launches it (E-252 #2593). Defaulting those
|
|
18
|
+
* to development pointed a real user's tools at http://localhost:8080/api and
|
|
19
|
+
* failed with a connection error that says nothing about why.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const BUILD_ENV = process.env.BUILD_ENV || 'production';
|
|
23
|
+
|
|
24
|
+
let config;
|
|
25
|
+
|
|
26
|
+
if (BUILD_ENV === 'production') {
|
|
27
|
+
config = await import('./production.js');
|
|
28
|
+
} else if (BUILD_ENV === 'staging') {
|
|
29
|
+
config = await import('./staging.js');
|
|
30
|
+
} else {
|
|
31
|
+
config = await import('./development.js');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const CONFIG = config.CONFIG;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Production Environment Configuration
|
|
3
|
+
*
|
|
4
|
+
* This config is baked into the production build at compile time.
|
|
5
|
+
* No runtime environment variables needed (except EZMODO_API_KEY).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const CONFIG = {
|
|
9
|
+
environment: 'production',
|
|
10
|
+
apiUrl: 'https://ezmodo.com/api',
|
|
11
|
+
webUrl: 'https://ezmodo.com',
|
|
12
|
+
settingsUrl: 'https://ezmodo.com/settings/api-keys',
|
|
13
|
+
|
|
14
|
+
// Environment URLs for reference/docs
|
|
15
|
+
environmentUrls: {
|
|
16
|
+
production: 'https://ezmodo.com/api',
|
|
17
|
+
}
|
|
18
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Staging Environment Configuration
|
|
3
|
+
*
|
|
4
|
+
* This config is baked into the staging build at compile time.
|
|
5
|
+
* No runtime environment variables needed (except EZMODO_API_KEY).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const CONFIG = {
|
|
9
|
+
environment: 'staging',
|
|
10
|
+
apiUrl: 'https://staging.ezmodo.com/api',
|
|
11
|
+
webUrl: 'https://staging.ezmodo.com',
|
|
12
|
+
settingsUrl: 'https://staging.ezmodo.com/settings/api-keys',
|
|
13
|
+
|
|
14
|
+
// Environment URLs for reference/docs
|
|
15
|
+
environmentUrls: {
|
|
16
|
+
staging: 'https://staging.ezmodo.com/api',
|
|
17
|
+
}
|
|
18
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity Access Handlers (#2170)
|
|
3
|
+
*
|
|
4
|
+
* Thin wrappers over /mcp/v1/access — the polymorphic entity-access surface.
|
|
5
|
+
* All authorization happens server-side: the Go handlers share one
|
|
6
|
+
* implementation with the REST routes (api/internal/api/handlers/access_ops.go),
|
|
7
|
+
* so the permission thresholds cannot differ between the two transports and
|
|
8
|
+
* there is nothing to enforce here.
|
|
9
|
+
*
|
|
10
|
+
* What this layer DOES owe the caller is honesty about parameters. Every field
|
|
11
|
+
* below is forwarded to an endpoint that uses it, and a field that belongs to a
|
|
12
|
+
* different action is rejected rather than dropped — the failure mode this whole
|
|
13
|
+
* line of work exists to eliminate is a parameter that is advertised, accepted,
|
|
14
|
+
* and silently discarded (#2168, #2169, #2171).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { callZephlyAPI } from '../lib/http-client.js';
|
|
18
|
+
|
|
19
|
+
// Fields that only mean something for one action. Passing one to a different
|
|
20
|
+
// action is a mistake worth surfacing: silently ignoring `role` on a
|
|
21
|
+
// remove_entry call would look exactly like a successful role change.
|
|
22
|
+
const ACTION_ONLY_FIELDS = {
|
|
23
|
+
update_settings: ['inheritFromParent', 'accessList', 'publicAccess'],
|
|
24
|
+
add_entry: ['userId', 'teamId', 'role'],
|
|
25
|
+
remove_entry: ['entryId'],
|
|
26
|
+
check: ['requiredRole'],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const ALL_ACTIONS = Object.keys(ACTION_ONLY_FIELDS);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Reject fields belonging to another action instead of dropping them.
|
|
33
|
+
*/
|
|
34
|
+
function rejectForeignFields(action, params) {
|
|
35
|
+
const allowed = new Set(ACTION_ONLY_FIELDS[action]);
|
|
36
|
+
const foreign = [];
|
|
37
|
+
|
|
38
|
+
for (const [otherAction, fields] of Object.entries(ACTION_ONLY_FIELDS)) {
|
|
39
|
+
if (otherAction === action) continue;
|
|
40
|
+
for (const field of fields) {
|
|
41
|
+
if (params[field] !== undefined && !allowed.has(field)) {
|
|
42
|
+
foreign.push(`${field} (belongs to action "${otherAction}")`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (foreign.length > 0) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`manage_access action "${action}" does not use: ${foreign.join(', ')}. ` +
|
|
50
|
+
'Remove the field or use the action it belongs to — it would otherwise be ' +
|
|
51
|
+
'accepted and discarded, which reads as a successful change.'
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Read an entity's access settings. Requires viewer access to the entity.
|
|
58
|
+
*/
|
|
59
|
+
export async function getAccess({ entityType, entityId, organizationId }) {
|
|
60
|
+
return callZephlyAPI('mcpGetAccess', { entityType, entityId, organizationId });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Dispatch manage_access to the appropriate operation.
|
|
65
|
+
*/
|
|
66
|
+
export async function manageAccess(args) {
|
|
67
|
+
const { action, ...params } = args;
|
|
68
|
+
|
|
69
|
+
if (!ALL_ACTIONS.includes(action)) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Unknown action: ${action}. Expected one of: ${ALL_ACTIONS.join(', ')}.`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
rejectForeignFields(action, params);
|
|
76
|
+
|
|
77
|
+
switch (action) {
|
|
78
|
+
case 'update_settings': return updateAccessSettings(params);
|
|
79
|
+
case 'add_entry': return addAccessEntry(params);
|
|
80
|
+
case 'remove_entry': return removeAccessEntry(params);
|
|
81
|
+
case 'check': return checkAccess(params);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Replace an entity's access configuration. Requires admin on the entity and
|
|
87
|
+
* the write:access scope.
|
|
88
|
+
*/
|
|
89
|
+
async function updateAccessSettings({
|
|
90
|
+
entityType, entityId, organizationId, inheritFromParent, accessList, publicAccess,
|
|
91
|
+
}) {
|
|
92
|
+
const body = { entityType, entityId, organizationId };
|
|
93
|
+
// Only forward what was actually set: these are tri-state server-side
|
|
94
|
+
// (unset means "leave alone"), so sending explicit nulls would clear them.
|
|
95
|
+
if (inheritFromParent !== undefined) body.inheritFromParent = inheritFromParent;
|
|
96
|
+
if (accessList !== undefined) body.accessList = accessList;
|
|
97
|
+
if (publicAccess !== undefined) body.publicAccess = publicAccess;
|
|
98
|
+
|
|
99
|
+
if (Object.keys(body).length === 3) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
'manage_access action "update_settings" needs at least one of ' +
|
|
102
|
+
'inheritFromParent, accessList, or publicAccess.'
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return callZephlyAPI('mcpUpdateAccess', body);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Grant one user or team a role. Requires admin on the entity and the
|
|
111
|
+
* write:access scope.
|
|
112
|
+
*/
|
|
113
|
+
async function addAccessEntry({ entityType, entityId, organizationId, userId, teamId, role }) {
|
|
114
|
+
const body = { entityType, entityId, organizationId, role };
|
|
115
|
+
if (userId) body.userId = userId;
|
|
116
|
+
if (teamId) body.teamId = teamId;
|
|
117
|
+
return callZephlyAPI('mcpAddAccessEntry', body);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Revoke a single access entry. Requires admin on the entity and the
|
|
122
|
+
* write:access scope.
|
|
123
|
+
*/
|
|
124
|
+
async function removeAccessEntry({ entityType, entityId, organizationId, entryId }) {
|
|
125
|
+
return callZephlyAPI('mcpRemoveAccessEntry', {
|
|
126
|
+
entityType,
|
|
127
|
+
entityId,
|
|
128
|
+
organizationId,
|
|
129
|
+
entryId,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Report whether the calling key's user holds a role on the entity. A denial is
|
|
135
|
+
* a normal successful response carrying granted:false.
|
|
136
|
+
*/
|
|
137
|
+
async function checkAccess({ entityType, entityId, organizationId, requiredRole }) {
|
|
138
|
+
const body = { entityType, entityId, organizationId };
|
|
139
|
+
if (requiredRole) body.requiredRole = requiredRole;
|
|
140
|
+
return callZephlyAPI('mcpCheckAccess', body);
|
|
141
|
+
}
|