@acarmisc/backstage-plugin-litellm-backend 0.30.0 → 0.30.1

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.
@@ -119,7 +119,15 @@ async function createKeyForUser(user, input, ctx) {
119
119
  if (!input.team_id) {
120
120
  throw new KeyServiceError(400, { error: 'team_id is required when mcp_servers is set' });
121
121
  }
122
- const disallowed = (0, mcpAccess_1.findDisallowedMcpServers)(mcpServers, (0, mcpAccess_1.teamMcpServers)(await getTeam(input.team_id)));
122
+ const allowed = (0, mcpAccess_1.teamMcpServers)(await getTeam(input.team_id));
123
+ let disallowed = (0, mcpAccess_1.findDisallowedMcpServers)(mcpServers, allowed);
124
+ if (disallowed.length > 0) {
125
+ // LiteLLM stores a team's grant by server id while callers name servers, so
126
+ // resolve the gateway's listing before refusing. A failed listing keeps the
127
+ // plain comparison.
128
+ const servers = await client.listMcpServers().catch(() => []);
129
+ disallowed = (0, mcpAccess_1.findDisallowedMcpServers)(mcpServers, allowed, servers);
130
+ }
123
131
  if (disallowed.length > 0) {
124
132
  throw new KeyServiceError(403, {
125
133
  error: 'One or more requested MCP servers are not allowed',
@@ -16,5 +16,13 @@ export declare function teamMcpServers(team: {
16
16
  mcp_servers?: string[];
17
17
  };
18
18
  } | undefined): string[];
19
- /** Requested names that are not in `allowed`, de-duplicated. */
20
- export declare function findDisallowedMcpServers(requested: string[], allowed: string[]): string[];
19
+ /**
20
+ * Requested names that are not granted, de-duplicated. A team's list may hold a
21
+ * server's id while a caller names it (or the reverse), so a request also counts
22
+ * as granted when the gateway's listing links its name and id to an allowed entry.
23
+ * Without `servers` only exact matches count.
24
+ */
25
+ export declare function findDisallowedMcpServers(requested: string[], allowed: string[], servers?: Array<{
26
+ id: string;
27
+ name?: string;
28
+ }>): string[];
@@ -21,8 +21,15 @@ function teamMcpServers(team) {
21
21
  const listed = team?.object_permission?.mcp_servers ?? [];
22
22
  return [...new Set(listed)].filter(n => backstage_plugin_litellm_common_1.MCP_SERVER_NAME_RE.test(n));
23
23
  }
24
- /** Requested names that are not in `allowed`, de-duplicated. */
25
- function findDisallowedMcpServers(requested, allowed) {
24
+ /**
25
+ * Requested names that are not granted, de-duplicated. A team's list may hold a
26
+ * server's id while a caller names it (or the reverse), so a request also counts
27
+ * as granted when the gateway's listing links its name and id to an allowed entry.
28
+ * Without `servers` only exact matches count.
29
+ */
30
+ function findDisallowedMcpServers(requested, allowed, servers = []) {
26
31
  const ok = new Set(allowed);
27
- return [...new Set(requested)].filter(n => !ok.has(n));
32
+ const granted = (n) => ok.has(n) ||
33
+ servers.some(s => (s.name === n || s.id === n) && (ok.has(s.id) || (s.name !== undefined && ok.has(s.name))));
34
+ return [...new Set(requested)].filter(n => !granted(n));
28
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acarmisc/backstage-plugin-litellm-backend",
3
- "version": "0.30.0",
3
+ "version": "0.30.1",
4
4
  "description": "The Backstage backend plugin for LiteLLM governance",
5
5
  "backstage": {
6
6
  "role": "backend-plugin",