@pyai/mcp 0.4.0 → 0.5.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/AGENT_GUIDE.md CHANGED
@@ -72,3 +72,15 @@ the corresponding session. Read it with `omni_get_call`, `omni_get_summary`,
72
72
  `omni_get_transcript`, or `omni_get_recording` with `response: "url"`. Recording
73
73
  URLs require REST authentication and are not public download links. For inbound
74
74
  calls use `omni_list_calls`, filtering by `session_label`.
75
+
76
+ ## Discovery and identity output
77
+
78
+ In MCP 0.5.0, use `discover_tools({"query":"voice agent"})`; `task` is an
79
+ alias. If both are present, they must match after trimming. Omit both to list
80
+ workflows. Empty or conflicting values return `invalid_arguments`.
81
+
82
+ `whoami({})` returns concise text showing environment, scopes and account
83
+ limits. Authorized organization, project and key IDs remain in structured
84
+ output; `whoami({"verbose":true})` also includes them in text. This reduces
85
+ prompt clutter, not host access to identity metadata. Credential-shaped fields
86
+ are omitted in both modes. Never paste keys or full identity dumps into reports.
package/README.md CHANGED
@@ -126,3 +126,15 @@ Calling reaches real phone destinations and is not a sandbox simulation.
126
126
  Use the SDK's `pyai.omni.connect({ rate, configure })` for realtime audio. The
127
127
  [Omni frame contract](https://api.pyai.com/omni-frames.json) is generated from
128
128
  the existing AsyncAPI protocol source.
129
+
130
+ ## Discovery and identity output
131
+
132
+ In MCP 0.5.0, use `discover_tools({"query":"voice agent"})`; `task` is an
133
+ alias. If both are present, they must match after trimming. Omit both to list
134
+ workflows. Empty or conflicting values return `invalid_arguments`.
135
+
136
+ `whoami({})` returns concise text showing environment, scopes and account
137
+ limits. Authorized organization, project and key IDs remain in structured
138
+ output; `whoami({"verbose":true})` also includes them in text. This reduces
139
+ prompt clutter, not host access to identity metadata. Credential-shaped fields
140
+ are omitted in both modes. Never paste keys or full identity dumps into reports.
@@ -18,6 +18,7 @@ export const EXTRA_TOOLS = [
18
18
  description: "Find the right PyAI tools and a short recipe for a use case: speech, STT, voice agents, compliance, summaries, dubbing or narration. No credentials or usage.",
19
19
  inputSchema: obj({
20
20
  query: string("Product name or task, e.g. “translate audio”, “voice agent”, “TTS”."),
21
+ task: string("Alias for query. If both are supplied, they must match after trimming."),
21
22
  }),
22
23
  },
23
24
  {
package/dist/server.js CHANGED
@@ -2,7 +2,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
2
  import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, McpError, ErrorCode, } from "@modelcontextprotocol/sdk/types.js";
3
3
  import { TOOLS, TOOL_SCOPES, callTool, localContext, } from "./tools.js";
4
4
  import { GUIDE, RESOURCES, SERVER_INSTRUCTIONS, WORKFLOWS, } from "./workflows.js";
5
- export const MCP_VERSION = "0.4.0";
5
+ export const MCP_VERSION = "0.5.0";
6
6
  export function createMcpServer(client, context = localContext) {
7
7
  const server = new Server({
8
8
  name: "pyai",
package/dist/tools.js CHANGED
@@ -45,6 +45,28 @@ function text(value) {
45
45
  : { text: s },
46
46
  };
47
47
  }
48
+ // Keep authorized resource IDs available to integrations without filling the
49
+ // default conversational text with identity metadata. Never return credential fields.
50
+ function identityResult(value, verbose) {
51
+ function safe(item) {
52
+ if (Array.isArray(item))
53
+ return item.map(safe);
54
+ if (!item || typeof item !== "object")
55
+ return item;
56
+ return Object.fromEntries(Object.entries(item).filter(([key, entry]) => !/secret|password|authorization|access_?token|refresh_?token|api_?key|token_ref/i.test(key)
57
+ && !(typeof entry === "string" && /^(key|token)$/i.test(key))).map(([key, entry]) => [key, safe(entry)]));
58
+ }
59
+ const identity = safe(value);
60
+ const summary = Object.fromEntries([
61
+ "env", "status", "org_status", "scopes", "expires_at", "plan", "limits", "credit", "key_budget_cents",
62
+ ].filter(key => Object.hasOwn(identity, key)).map(key => [key, identity[key]]));
63
+ return {
64
+ content: [{ type: "text", text: JSON.stringify(verbose ? identity : {
65
+ ...summary, details: "Authorized resource IDs are in structuredContent. Use verbose=true to include identity details in text.",
66
+ }, null, 2) }],
67
+ structuredContent: identity,
68
+ };
69
+ }
48
70
  /** Tools that need a configured key. create_sandbox_key + get_started do not. */
49
71
  const KEY_REQUIRED = new Set([
50
72
  "list_models",
@@ -100,10 +122,10 @@ const LEGACY_TOOLS = [
100
122
  {
101
123
  name: "whoami",
102
124
  description: "Introspect the active key (GET /v1/me): org, environment (test/live), granted scopes, " +
103
- "and rate-limit/credit posture. Use it to self-diagnose a 401/403/402.",
125
+ "and rate-limit/credit posture. Text is concise; authorized IDs remain in structuredContent. Use verbose=true for identity details. Use it to self-diagnose a 401/403/402.",
104
126
  inputSchema: {
105
127
  type: "object",
106
- properties: {},
128
+ properties: { verbose: { type: "boolean", description: "Include authorized identity details in text. Credentials are always omitted.", default: false } },
107
129
  additionalProperties: false,
108
130
  },
109
131
  },
@@ -464,7 +486,12 @@ async function executeTool(name, args, client, ctx) {
464
486
  },
465
487
  };
466
488
  if (name === "discover_tools") {
467
- const words = String(args.query ?? "")
489
+ const query = typeof args.query === "string" ? args.query.trim() : undefined;
490
+ const task = typeof args.task === "string" ? args.task.trim() : undefined;
491
+ if (query === "" || task === "" || (query !== undefined && task !== undefined && query !== task)) {
492
+ return toolError("Use a non-empty query or task. If both are supplied, they must match after trimming.", "invalid_arguments");
493
+ }
494
+ const words = String(query ?? task ?? "")
468
495
  .toLowerCase()
469
496
  .match(/[a-z0-9]+/g)
470
497
  ?.filter((w) => ![
@@ -549,7 +576,7 @@ async function executeTool(name, args, client, ctx) {
549
576
  });
550
577
  }
551
578
  case "whoami":
552
- return text(await client.getJson("/v1/me"));
579
+ return identityResult(await client.getJson("/v1/me"), args.verbose === true);
553
580
  case "list_models":
554
581
  return text(await client.getJson("/v1/models"));
555
582
  case "list_voices": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyai/mcp",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "PyAI MCP: OAuth-connected voice agents, speech, transcription, compliance, call summaries, dubbing and expressive narration for chat apps and coding agents.",
5
5
  "type": "module",
6
6
  "license": "MIT",