@topolo/mcp 0.3.0 → 0.4.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.
package/README.md CHANGED
@@ -112,7 +112,10 @@ clients surface that as a failed server launch.
112
112
  `topolo_api_call` is the escape hatch for endpoints without typed MCP tools
113
113
  yet. GET requests work without confirmation; mutating HTTP methods must pass
114
114
  `confirm: true` or the SDK rejects the call. Backing services still enforce
115
- permissions for every request.
115
+ permissions for every request. The tool schema intentionally does not embed a
116
+ large service enum; agents should call `topolo_list_services` first, then pass a
117
+ service ID/slug. The SDK can resolve live Auth-catalog API/technical service
118
+ surfaces for newly registered services before the next MCP package release.
116
119
 
117
120
  ## Safety rails
118
121
 
@@ -159,11 +162,12 @@ waiting for JSON-RPC on stdin. To drive it manually, use the MCP Inspector:
159
162
  npx @modelcontextprotocol/inspector node dist/index.js
160
163
  ```
161
164
 
162
- ## Release flow
165
+ ## Release Flow
163
166
 
164
- Pushing a `v<x.y.z>` tag to `main` triggers GitHub Actions to typecheck, test,
165
- build, verify the tag matches `package.json`, and publish to npm via the
166
- `NPM_TOKEN` secret. No developer-machine credentials involved.
167
+ Preferred flow: push a `v<x.y.z>` tag to `main`; GitHub Actions typechecks,
168
+ tests, builds, verifies the tag matches `package.json`, and publishes to npm via
169
+ the `NPM_TOKEN` secret. Operators may publish directly with an npm token only
170
+ after running the same package checks and `npm pack --dry-run`.
167
171
 
168
172
  ## Phase 2 (planned)
169
173
 
package/dist/dispatch.js CHANGED
@@ -52,7 +52,6 @@ function splitService(servicePart) {
52
52
 
53
53
  // src/tools.ts
54
54
  var APPLICATION_IDS = Object.keys(APPLICATIONS).sort();
55
- var SERVICE_IDS = Object.keys(DEFAULT_SERVICE_URLS).sort();
56
55
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
57
56
  var TOOLS = [
58
57
  {
@@ -91,7 +90,7 @@ var TOOLS = [
91
90
  {
92
91
  name: "topolo_list_applications",
93
92
  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.",
93
+ 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
94
  requiredScopes: [],
96
95
  destructive: false,
97
96
  inputSchema: {
@@ -99,14 +98,14 @@ var TOOLS = [
99
98
  properties: {},
100
99
  additionalProperties: false
101
100
  },
102
- handler: async () => ({
103
- applications: APPLICATION_IDS.map((id) => APPLICATIONS[id])
101
+ handler: async (topolo) => ({
102
+ applications: await topolo.client.listServices()
104
103
  })
105
104
  },
106
105
  {
107
106
  name: "topolo_get_application",
108
107
  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.",
108
+ 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
109
  requiredScopes: [],
111
110
  destructive: false,
112
111
  inputSchema: {
@@ -114,21 +113,29 @@ var TOOLS = [
114
113
  properties: {
115
114
  application: {
116
115
  type: "string",
117
- enum: APPLICATION_IDS,
118
- description: "Application ID from topolo_list_applications."
116
+ description: "Application identifier: service id, slug, or name from topolo_list_applications."
119
117
  }
120
118
  },
121
119
  required: ["application"],
122
120
  additionalProperties: false
123
121
  },
124
- handler: async (_topolo, args) => {
122
+ handler: async (topolo, args) => {
125
123
  const application = args["application"];
126
- if (typeof application !== "string" || !APPLICATION_IDS.includes(application)) {
124
+ if (typeof application !== "string" || !application.trim()) {
125
+ throw new Error("`application` must be a non-empty string.");
126
+ }
127
+ const needle = application.trim().toLowerCase();
128
+ const catalog = await topolo.client.listServices();
129
+ const match = catalog.find(
130
+ (e) => e.serviceId.toLowerCase() === needle || e.slug && e.slug.toLowerCase() === needle || e.name && e.name.toLowerCase() === needle
131
+ );
132
+ if (!match) {
133
+ const known = catalog.map((e) => e.slug || e.serviceId).sort().filter((value) => typeof value === "string" && value.length > 0);
127
134
  throw new Error(
128
- `Unknown application "${String(application)}". Known: ${APPLICATION_IDS.join(", ")}`
135
+ `Unknown application "${application}". ${formatKnownValues(known, "applications")}`
129
136
  );
130
137
  }
131
- return { application: APPLICATIONS[application] };
138
+ return { application: match };
132
139
  }
133
140
  },
134
141
  {
@@ -142,7 +149,6 @@ var TOOLS = [
142
149
  properties: {
143
150
  application: {
144
151
  type: "string",
145
- enum: APPLICATION_IDS,
146
152
  description: "Optional application ID from topolo_list_applications."
147
153
  }
148
154
  },
@@ -160,7 +166,7 @@ var TOOLS = [
160
166
  }
161
167
  if (typeof application !== "string" || !APPLICATION_IDS.includes(application)) {
162
168
  throw new Error(
163
- `Unknown application "${String(application)}". Known: ${APPLICATION_IDS.join(", ")}`
169
+ `Unknown application "${String(application)}". ${formatKnownValues(APPLICATION_IDS, "applications")}`
164
170
  );
165
171
  }
166
172
  const app = APPLICATIONS[application];
@@ -183,7 +189,6 @@ var TOOLS = [
183
189
  properties: {
184
190
  application: {
185
191
  type: "string",
186
- enum: APPLICATION_IDS,
187
192
  description: "Optional application ID from topolo_list_applications."
188
193
  },
189
194
  failOn: {
@@ -208,7 +213,7 @@ var TOOLS = [
208
213
  }
209
214
  if (typeof application !== "string" || !APPLICATION_IDS.includes(application)) {
210
215
  throw new Error(
211
- `Unknown application "${String(application)}". Known: ${APPLICATION_IDS.join(", ")}`
216
+ `Unknown application "${String(application)}". ${formatKnownValues(APPLICATION_IDS, "applications")}`
212
217
  );
213
218
  }
214
219
  report = auditAllApplicationRequirements([application]);
@@ -241,7 +246,7 @@ var TOOLS = [
241
246
  {
242
247
  name: "topolo_api_call",
243
248
  title: "Call any Topolo platform service",
244
- description: `Low-level passthrough to any Topolo service. Use when no domain-specific tool exists for the operation you need. The request is signed with the current credential and scoped to the caller's organization \u2014 the target service will return 403 if your scopes do not permit the operation, even though this tool is advertised. Mutating calls (non-GET methods) require confirm:true. Known services: ${SERVICE_IDS.join(", ")}.`,
249
+ description: "Low-level passthrough to any Topolo service. Use when no domain-specific tool exists for the operation you need. The request is signed with the current credential and scoped to the caller's organization \u2014 the target service will return 403 if your scopes do not permit the operation, even though this tool is advertised. Mutating calls (non-GET methods) require confirm:true. Call topolo_list_services first to discover callable service IDs; the SDK can also resolve live Auth-catalog service ids/slugs for newly registered API surfaces.",
245
250
  requiredScopes: [],
246
251
  destructive: true,
247
252
  inputSchema: {
@@ -249,7 +254,6 @@ var TOOLS = [
249
254
  properties: {
250
255
  service: {
251
256
  type: "string",
252
- enum: SERVICE_IDS,
253
257
  description: "Target service ID from the platform registry."
254
258
  },
255
259
  method: {
@@ -282,8 +286,8 @@ var TOOLS = [
282
286
  handler: async (topolo, args) => {
283
287
  const service = args["service"];
284
288
  const path = args["path"];
285
- if (typeof service !== "string" || !SERVICE_IDS.includes(service)) {
286
- throw new Error(`Unknown service "${String(service)}". Known: ${SERVICE_IDS.join(", ")}`);
289
+ if (typeof service !== "string" || !service.trim()) {
290
+ throw new Error("service is required. Call topolo_list_services first to discover callable services.");
287
291
  }
288
292
  if (typeof path !== "string" || !path) {
289
293
  throw new Error("path is required");
@@ -298,7 +302,7 @@ var TOOLS = [
298
302
  Object.entries(query).map(([k, v]) => [k, String(v)])
299
303
  ) : void 0;
300
304
  return topolo.client.request({
301
- service,
305
+ service: service.trim(),
302
306
  method,
303
307
  path,
304
308
  body: args["body"],
@@ -371,6 +375,12 @@ function statusesAtOrWorse(threshold) {
371
375
  if (threshold === "needs_review") return ["missing", "needs_review"];
372
376
  return ["missing", "needs_review", "partial"];
373
377
  }
378
+ function formatKnownValues(values, label) {
379
+ const sorted = [...values].sort();
380
+ const preview = sorted.slice(0, 25).join(", ");
381
+ const suffix = sorted.length > 25 ? `, ... +${sorted.length - 25} more` : "";
382
+ return `Run discovery for the full ${label} list. Known snapshot sample: ${preview}${suffix}`;
383
+ }
374
384
 
375
385
  // src/dispatch.ts
376
386
  function listAvailableTopoloTools(scopes, tools = TOOLS) {