@topolo/mcp 0.3.0 → 0.4.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/dist/dispatch.js CHANGED
@@ -91,7 +91,7 @@ var TOOLS = [
91
91
  {
92
92
  name: "topolo_list_applications",
93
93
  title: "List every Topolo platform application",
94
- description: "Returns the generated Topolo application catalog, including pages-only apps, tooling apps, and API-backed apps. Use this before choosing a service-specific or generic API operation. Pure metadata: no credential introspection, no permission check, no external call.",
94
+ description: "Returns the live Topolo application catalog from the platform registry. Reflects apps as they are registered/removed with no client rebuild. Authenticated callers in an entitled org also see the developer_only apps their role permits. Falls back to a bundled snapshot only if the registry is unreachable.",
95
95
  requiredScopes: [],
96
96
  destructive: false,
97
97
  inputSchema: {
@@ -99,14 +99,14 @@ var TOOLS = [
99
99
  properties: {},
100
100
  additionalProperties: false
101
101
  },
102
- handler: async () => ({
103
- applications: APPLICATION_IDS.map((id) => APPLICATIONS[id])
102
+ handler: async (topolo) => ({
103
+ applications: await topolo.client.listServices()
104
104
  })
105
105
  },
106
106
  {
107
107
  name: "topolo_get_application",
108
108
  title: "Describe one Topolo platform application",
109
- description: "Returns one application catalog entry with its package name, production URL, deploy targets, and SDK service IDs when the app has callable APIs. Pure metadata: no credential introspection, no permission check, no external call.",
109
+ description: "Returns one live application catalog entry (service id, slug, name, base URL, status). Accepts the service id, slug, or name. Resolved against the live registry so newly-registered apps work without a client rebuild.",
110
110
  requiredScopes: [],
111
111
  destructive: false,
112
112
  inputSchema: {
@@ -114,21 +114,29 @@ var TOOLS = [
114
114
  properties: {
115
115
  application: {
116
116
  type: "string",
117
- enum: APPLICATION_IDS,
118
- description: "Application ID from topolo_list_applications."
117
+ description: "Application identifier: service id, slug, or name from topolo_list_applications."
119
118
  }
120
119
  },
121
120
  required: ["application"],
122
121
  additionalProperties: false
123
122
  },
124
- handler: async (_topolo, args) => {
123
+ handler: async (topolo, args) => {
125
124
  const application = args["application"];
126
- if (typeof application !== "string" || !APPLICATION_IDS.includes(application)) {
125
+ if (typeof application !== "string" || !application.trim()) {
126
+ throw new Error("`application` must be a non-empty string.");
127
+ }
128
+ const needle = application.trim().toLowerCase();
129
+ const catalog = await topolo.client.listServices();
130
+ const match = catalog.find(
131
+ (e) => e.serviceId.toLowerCase() === needle || e.slug && e.slug.toLowerCase() === needle || e.name && e.name.toLowerCase() === needle
132
+ );
133
+ if (!match) {
134
+ const known = catalog.map((e) => e.slug || e.serviceId).sort().join(", ");
127
135
  throw new Error(
128
- `Unknown application "${String(application)}". Known: ${APPLICATION_IDS.join(", ")}`
136
+ `Unknown application "${application}". Known: ${known}`
129
137
  );
130
138
  }
131
- return { application: APPLICATIONS[application] };
139
+ return { application: match };
132
140
  }
133
141
  },
134
142
  {
package/dist/index.js CHANGED
@@ -1516,12 +1516,12 @@ var APPLICATION_REQUIREMENTS = [
1516
1516
  summary: "Applications must derive identity, organization, scopes, and service entitlement from TopoloAuth rather than inventing parallel auth state.",
1517
1517
  appliesTo: ["browser", "api", "tooling", "agent_surface"],
1518
1518
  evidence: [
1519
- "@topolo/auth-client or @topolo/auth-middleware is used where applicable",
1519
+ "@topolo/auth-client or @topolo-io/worker-runtime is used where applicable",
1520
1520
  "No public command, tool, or SDK method accepts orgId",
1521
1521
  "Service slug and Auth service registration are documented for protected services"
1522
1522
  ],
1523
1523
  implementation: [
1524
- "Use shared auth packages instead of copying token parsing or browser storage flows",
1524
+ "Use shared auth/runtime packages instead of copying token parsing, gateway, or browser storage flows",
1525
1525
  "Derive organization from the credential on every request",
1526
1526
  "Route OAuth, refresh, logout, and SSO paths according to the shared Auth contract"
1527
1527
  ]
@@ -2062,6 +2062,58 @@ var TopoloClient = class _TopoloClient {
2062
2062
  });
2063
2063
  return value;
2064
2064
  }
2065
+ /**
2066
+ * List the platform service catalog from the live registry
2067
+ * (`GET /api/services/catalog` on TopoloAuth). Credential-aware: the
2068
+ * caller's credential is attached, so an authenticated caller in an
2069
+ * entitled org also receives the `developer_only` services its role
2070
+ * permits; an unauthenticated/agent caller receives `active` only.
2071
+ *
2072
+ * Resilient: on any network/HTTP failure this falls back to the
2073
+ * build-time generated `APPLICATIONS` snapshot so offline/degraded
2074
+ * callers still get a usable (if static) catalog. The live result is
2075
+ * authoritative; the snapshot is a floor, not a cap.
2076
+ */
2077
+ async listServices() {
2078
+ try {
2079
+ const res = await this.request({ service: "auth", method: "GET", path: "/api/services/catalog" });
2080
+ const raw = res.data?.services ?? res.services;
2081
+ if (Array.isArray(raw) && raw.length > 0) {
2082
+ const entries = [];
2083
+ for (const item of raw) {
2084
+ const s = item;
2085
+ const serviceId = typeof s.service_id === "string" ? s.service_id : typeof s.id === "string" ? s.id : null;
2086
+ if (!serviceId) continue;
2087
+ entries.push({
2088
+ serviceId,
2089
+ slug: typeof s.app_slug === "string" ? s.app_slug : null,
2090
+ name: typeof s.name === "string" ? s.name : null,
2091
+ description: typeof s.description === "string" ? s.description : null,
2092
+ baseUrl: typeof s.base_url === "string" ? s.base_url : null,
2093
+ status: typeof s.status === "string" ? s.status : null,
2094
+ surfaceKind: typeof s.surface_kind === "string" ? s.surface_kind : null,
2095
+ launchable: s.launchable === true
2096
+ });
2097
+ }
2098
+ if (entries.length > 0) return entries;
2099
+ }
2100
+ } catch {
2101
+ }
2102
+ return this.generatedCatalogFallback();
2103
+ }
2104
+ generatedCatalogFallback() {
2105
+ const apps = Object.values(APPLICATIONS);
2106
+ return apps.map((app) => ({
2107
+ serviceId: app.id,
2108
+ slug: app.id,
2109
+ name: app.name ?? app.id,
2110
+ description: null,
2111
+ baseUrl: app.productionUrl ?? null,
2112
+ status: "active",
2113
+ surfaceKind: "application",
2114
+ launchable: Boolean(app.productionUrl)
2115
+ }));
2116
+ }
2065
2117
  /**
2066
2118
  * Introspect the current credential. Returns the caller's resolved identity,
2067
2119
  * organization, and permission set. Used by CLI `whoami` and by MCP to gate
@@ -2322,7 +2374,7 @@ var TOOLS = [
2322
2374
  {
2323
2375
  name: "topolo_list_applications",
2324
2376
  title: "List every Topolo platform application",
2325
- description: "Returns the generated Topolo application catalog, including pages-only apps, tooling apps, and API-backed apps. Use this before choosing a service-specific or generic API operation. Pure metadata: no credential introspection, no permission check, no external call.",
2377
+ description: "Returns the live Topolo application catalog from the platform registry. Reflects apps as they are registered/removed with no client rebuild. Authenticated callers in an entitled org also see the developer_only apps their role permits. Falls back to a bundled snapshot only if the registry is unreachable.",
2326
2378
  requiredScopes: [],
2327
2379
  destructive: false,
2328
2380
  inputSchema: {
@@ -2330,14 +2382,14 @@ var TOOLS = [
2330
2382
  properties: {},
2331
2383
  additionalProperties: false
2332
2384
  },
2333
- handler: async () => ({
2334
- applications: APPLICATION_IDS.map((id) => APPLICATIONS[id])
2385
+ handler: async (topolo) => ({
2386
+ applications: await topolo.client.listServices()
2335
2387
  })
2336
2388
  },
2337
2389
  {
2338
2390
  name: "topolo_get_application",
2339
2391
  title: "Describe one Topolo platform application",
2340
- description: "Returns one application catalog entry with its package name, production URL, deploy targets, and SDK service IDs when the app has callable APIs. Pure metadata: no credential introspection, no permission check, no external call.",
2392
+ description: "Returns one live application catalog entry (service id, slug, name, base URL, status). Accepts the service id, slug, or name. Resolved against the live registry so newly-registered apps work without a client rebuild.",
2341
2393
  requiredScopes: [],
2342
2394
  destructive: false,
2343
2395
  inputSchema: {
@@ -2345,21 +2397,29 @@ var TOOLS = [
2345
2397
  properties: {
2346
2398
  application: {
2347
2399
  type: "string",
2348
- enum: APPLICATION_IDS,
2349
- description: "Application ID from topolo_list_applications."
2400
+ description: "Application identifier: service id, slug, or name from topolo_list_applications."
2350
2401
  }
2351
2402
  },
2352
2403
  required: ["application"],
2353
2404
  additionalProperties: false
2354
2405
  },
2355
- handler: async (_topolo, args) => {
2406
+ handler: async (topolo, args) => {
2356
2407
  const application = args["application"];
2357
- if (typeof application !== "string" || !APPLICATION_IDS.includes(application)) {
2408
+ if (typeof application !== "string" || !application.trim()) {
2409
+ throw new Error("`application` must be a non-empty string.");
2410
+ }
2411
+ const needle = application.trim().toLowerCase();
2412
+ const catalog = await topolo.client.listServices();
2413
+ const match = catalog.find(
2414
+ (e) => e.serviceId.toLowerCase() === needle || e.slug && e.slug.toLowerCase() === needle || e.name && e.name.toLowerCase() === needle
2415
+ );
2416
+ if (!match) {
2417
+ const known = catalog.map((e) => e.slug || e.serviceId).sort().join(", ");
2358
2418
  throw new Error(
2359
- `Unknown application "${String(application)}". Known: ${APPLICATION_IDS.join(", ")}`
2419
+ `Unknown application "${application}". Known: ${known}`
2360
2420
  );
2361
2421
  }
2362
- return { application: APPLICATIONS[application] };
2422
+ return { application: match };
2363
2423
  }
2364
2424
  },
2365
2425
  {
@@ -2604,7 +2664,7 @@ function statusesAtOrWorse(threshold) {
2604
2664
  }
2605
2665
 
2606
2666
  // src/version.ts
2607
- var MCP_VERSION = "0.3.0";
2667
+ var MCP_VERSION = "0.4.0";
2608
2668
 
2609
2669
  // src/server.ts
2610
2670
  function buildServer(topolo, scopes, tools = TOOLS) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topolo/mcp",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Model Context Protocol server for the Topolo platform. Exposes scope-gated tools that third-party agents (Claude, Codex, etc.) can call natively.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,7 +21,8 @@
21
21
  "typecheck": "tsc --noEmit",
22
22
  "start": "node dist/index.js",
23
23
  "test": "vitest run",
24
- "test:watch": "vitest"
24
+ "test:watch": "vitest",
25
+ "coverage": "vitest run --coverage"
25
26
  },
26
27
  "dependencies": {
27
28
  "@modelcontextprotocol/sdk": "^1.0.0"
@@ -29,6 +30,7 @@
29
30
  "devDependencies": {
30
31
  "@topolo/sdk": "workspace:*",
31
32
  "@types/node": "^20.12.0",
33
+ "@vitest/coverage-v8": "^2.1.0",
32
34
  "tsup": "^8.0.0",
33
35
  "typescript": "^5.4.0",
34
36
  "vitest": "^2.1.0"