Contents
I submitted the first version of the Call Trace plugin to OpenAI for review and received a rejection. The response said that the app’s purpose or some of its behavior conflicted with the usage policies. Before I could publish it, I had to reconsider which capabilities and data to expose to the AI client.
Call Trace is my service for working with call recordings. The plugin lets a user find a recording, inspect its basic details, and retrieve a transcript directly in ChatGPT or Codex. That simple workflow depends on the model choosing tools, the user authorizing access, and the server enforcing its boundaries.
This is what I changed after the rejection, how I designed the exchange of data, and how I validated the integration. I will also cover the changes that became necessary after publication.
The user workflow
A user already has call recordings in Call Trace. Working with them from a chat requires a few clear actions: find a recording, inspect its basic details, and open the transcript.
For example:
Show my 10 most recent completed calls.
Then:
Open the transcript for the call with this ID.
Once the text is available, the user can ask ChatGPT to summarize it or extract agreements with supporting quotations. There are two distinct operations here: the Call Trace server returns the transcript, and the model works with that text. The public plugin has no separate summarization tool.
This workflow starts with an ordinary question and leads the user to the relevant material. Access remains tied to the authorized Call Trace account and company.
Three things have to line up: the user’s request, the tool the model chooses, and the data the server is allowed to return. A mistake at any of those stages affects the final answer.
What changed after the rejection
After version 1.0.0 was rejected, I reduced the public integration’s scope. That was my remediation decision: OpenAI’s letter described the policy issue in general terms.
The early version included tools for detailed call analysis and team metrics. They exposed scores, employee information, and other internal analytics. During the revision, I removed public access to employee identifiers, scores, rankings, emotion analysis, and comparisons of team performance.
Three actions remained: find a recording, read its minimal metadata, and retrieve the transcript of a specific conversation.
This experience helped me draw a clearer boundary between the main product and its plugin. A service may contain many features, but every capability exposed to an AI client needs a separate assessment: why it is needed, what data it reveals, and what conclusions it makes possible.
The reduction also changed the server response. Even when an internal record contains additional fields, a public tool returns only the explicitly allowed set.
This case does not establish a blanket prohibition on call analytics. It shows how feedback on a particular integration can require changes to its scope. I would now define that boundary earlier: the external contract should contain the data required for the stated workflow.
How the AI client communicates with the service
The public Call Trace integration uses MCP, the Model Context Protocol. It lets an AI client discover which actions a server provides, which arguments they accept, and what results to expect.
The Call Trace MCP server uses Streamable HTTP over HTTPS. Its endpoint is https://www.call-trace.com/mcp, and access to data is granted through OAuth.
After authorization, a request passes through several stages:
“Show my most recent completed calls”
OAuth binds the connection to a confirmed account and company.
- 01AI clientChatGPT / Codex
Selects a tool and supplies its arguments.
- 02Access checkCall Trace MCP
Validates the token, permissions, and company.
- 03Data sourceWorkspace
Returns records from the authorized company.
Call Trace selects permitted fields and masks supported sensitive patterns in the text.
- Read only
- Company scoped
- Minimal data
I built the plugin without a custom in-chat widget. The server provides tools and data, while the AI client handles the conversation. That was enough for the three available actions.
It helps to distinguish the components. The MCP server performs operations. OAuth determines who can access which data. The plugin package contains installation and connection information. The directory helps users find and install that package.
OpenAI’s current documentation describes a package as a collection of skills, MCP connections, and other resources. Its contents depend on the task: a custom interface and bundled skills are not necessary for every plugin. Plugin packaging.
Three tools with clear boundaries
I kept the public contract small:
| Tool | Purpose | What the client receives |
|---|---|---|
search_calls |
Find recordings by status, queue, creation time, or a fragment of the filename or external recording ID (reference) |
A list with minimal metadata |
get_call |
Retrieve one call’s details by ID | ID, status, queue, creation time, and transcript availability |
get_transcript |
Retrieve the text of a specific call | ID, transcript with supported sensitive patterns redacted, and a flag indicating whether redaction was applied |
Search returns the newest records first: ten by default, with a maximum of twenty-five per call. This limits the size of a single response and makes the results easier to use in subsequent steps.
For example, “show my ten most recent completed calls” should lead to a search_calls invocation with status done and a limit of 10. Once a recording has been selected, the model uses its ID to retrieve the transcript. The server handles retrieval and access checks; the model chooses the action and explains the result to the user. Those parts of the interaction need separate validation.
Search has an important limitation: search_calls does not perform semantic search across all transcripts. A request such as “find every call that discussed a refund” cannot be answered exhaustively by this tool alone. The available sample must first be identified, the relevant texts retrieved, and the records actually examined made clear to the user.
Each tool description should explain its purpose, inputs, and limitations. The model uses that information to choose an action. The accuracy of the description therefore affects the integration’s behavior as directly as the accuracy of its API schema. Defining tools.
A practical exercise I recommend is to pair each successful example with a similar request the tool cannot fulfill. This quickly exposes promises that are broader than the implementation.
Data access boundaries
The first restriction is access only to the authorized company’s data. The server derives the company ID from the validated authorization context. The model cannot choose it as a tool argument.
This applies to both search and individual-record requests. Knowing another company’s callId must not, by itself, grant access to that call.
The second restriction is the size of the response contract. A call list does not need raw provider metadata, audio files, or employee information. The public contract contains only the fields needed for the stated actions.
The third is redaction before text leaves the server. Transcripts are processed for known sensitive patterns, such as common email addresses, phone numbers, and certain formats of payment details and secrets.
This is not complete anonymization. Pattern-based redaction cannot guarantee the removal of every name, indirect identifier, or unusually formatted value. It needs tests based on representative product data and a precise explanation of its limits.
All three tools declare readOnlyHint: true, destructiveHint: false, and openWorldHint: false. They read data within the authorized company, do not modify records, and do not access arbitrary external resources.
These annotations communicate a tool’s properties to the client. Access checks and server code enforce the actual restrictions. Hosting an MCP server on the internet does not, by itself, imply openWorldHint: true; the annotation describes the scope of the tool’s actions. OpenAI annotation reference.
Connecting an account through OAuth
For Call Trace, I used OAuth authorization code flow with PKCE and dynamic client registration. The plugin requests the limited scope analysis:read.
PKCE binds the exchange of an authorization code for a token to the client that started the authorization flow. The detail is invisible to the user, but the server must validate it alongside request expiry, the allowed redirect URI, and access permissions.
OpenAI’s documentation also describes discovery of authorization settings and a 401 response pointing to protected-resource metadata. PKCE requires support for S256. Dynamic registration, which I used, is one supported way to configure a client; it should not be treated as the only option for every project. MCP authentication in OpenAI.
It is useful to answer practical questions while designing this flow: what happens when a request expires, consent is submitted twice, the account changes, a token is refreshed, or a connection is revoked? Each state needs a clear outcome for both the server and the person using it.
Validating tools and authorization
I expanded the checks throughout development and subsequent changes after release. Validation ultimately covered several levels: the server’s tool contract, authorization, and the integration’s behavior in a real AI client. The first two received separate automated checks.
Contract verification checks the exact tool list, response schemas, annotations, excluded fields, and expected redaction behavior for prepared examples. It helps catch internal API changes that accidentally expand the public response.
For OAuth, I tested registration and activation in a new tab, resending activation emails, switching accounts, simultaneous approval, invalid PKCE, authorization-code replay, token refresh, revocation, request expiry, and administrator permissions.
An acceptance checklist should include the following behavior:
| Scenario | Expected result |
|---|---|
| The user is not signed in | The connection flow continues after sign-in |
| The activation email opens in a new tab | The original connection request is restored |
| The account changes before consent | Access cannot silently be granted from another account |
| A call ID belongs to another company | That company’s data is not returned |
| An authorization code is used again | The repeated exchange is rejected |
| The user revokes the connection | Tokens for that connection no longer grant access |
| The connection request expires | The user receives a clear instruction to restart |
I moved checks that create users and companies to a separate test database. The test also requires an explicitly approved test hostname and stops if the configuration does not match.
Another lesson came during the release of the OAuth changes: the local database configuration differed from the actual production configuration. A migration therefore needs verification through the connection used by the deployed application. A successful migration command in a terminal does not establish that the correct database was updated.
The interaction with the model also needs its own check: which action it selected, which arguments it sent, and whether the answer matches the data it received. A local test with a custom callback cannot reproduce the interface and behavior of ChatGPT or Codex. Installation and authorization from the actual client need a separate acceptance pass.
Preparing the OpenAI review materials
For Call Trace, I documented five positive and three negative scenarios. The positive cases covered recent completed calls, queue and date filters, one record’s metadata, its transcript, and lookup by an explicit reference.
The negative cases tested boundaries: requesting another company’s data, ranking employees, and attempting to use emotion analysis for an employment decision. Their purpose is to check whether the integration respects its stated limits.
I prepared a separate demonstration using synthetic calls. A reviewer should be able to reproduce every step without accessing real customer conversations.
As of November 2, 2026, OpenAI’s submission instructions require a dedicated demo account, five positive and three negative tests, a video walkthrough, and release notes for MCP review. The reviewer account must work without MFA, one-time codes, or magic links. A stable public HTTPS server and domain verification are also required. Preparing and submitting a plugin.
Keeping a small review package alongside the code is useful: capabilities, tool schemas, example requests, expected results, limitations, and a demonstration script. This makes it easier to keep changes to the contract aligned with the directory listing and tests.
Publication in the directory
After the revisions, I published Call Trace in the public directory. The plugin listing and connection guide are the entry points for users.
It is useful to distinguish the distribution states. Adding a plugin to a local marketplace enables installation and testing in the corresponding environment. Publishing it within a workspace restricts distribution to that organization. The public directory has a separate process. Distribution options.
In OpenAI’s public process, submission, approval, and publication are separate steps. Approval is followed by the Publish plugin action. Publication workflow.
For MCP tool updates, the documentation recommends running Rescan and checking which changes are available. New tools become available after approval. A delayed review of an existing tool’s changes can leave the client with the previous approved schema, so the server needs to remain compatible with that schema until the update. Updating an MCP server.
Once the plugin was in the directory, the next check was connection through a real user workflow. That exposed a problem which an MCP server response alone could not reveal.
Problems after publication
After publication, I encountered a situation that is easy to miss during development: a user installs the plugin, completes part of the connection flow, and cannot tell which service account it is connected to.
Installation and authorization are separate stages. Someone can arrive from the directory without an active Call Trace session, register for the first time, open an activation email in another tab, or discover that they are signed in to the wrong account.
I rebuilt the connection flow as an explicit sequence:
- Sign in or register.
- Check the displayed email address and company.
- Approve read-only access.
- Return to the AI client.
The connection request is stored on the server. The browser receives an opaque flow identifier, and completing email verification restores the appropriate step. Continuation therefore does not depend solely on the original tab’s state.
Before granting access, the server checks again that the current user and company match those displayed at consent. Approving a connection requires company administrator permissions.
The integrations page shows who granted access, which company the connection belongs to, when it was used, and how to revoke it. A connection with no requests is shown as waiting for its first request.
This distinction matters for diagnosis: granting permission and successfully retrieving data are separate events.
Where I would start the next integration
If I went through this process again, I would use the following order:
- Choose a concrete workflow. Describe the user’s question and the useful result they should receive.
- Define the minimum set of tools. Record each tool’s arguments, returned fields, and limitations.
- Design access controls. Tie requests to the authorized company and check permission for every record.
- Connect as a new user. Start without an active session, then test registration, an email opened in another tab, and account switching.
- Prepare demo data. Make the scenarios reproducible without real customer conversations.
- Test the boundaries. Request another company’s record, an unsupported action, and data the contract should not return.
- Align the code and submission materials. The description, video, and expected results should match the actual capabilities.
- Repeat the entire flow after publication. Install the plugin, authorize it, retrieve data, and revoke the connection.
The public plugin ended up with three tools and defined access boundaries. The rejection made me reconsider the data exposed, testing made the allowed behavior explicit, and the experience after publication led to a clearer account connection flow. For my next integration, I would validate the entire chain from the beginning: the person’s request, the model’s tool choice, authorization, the server response, and revocation.
Links: Call Trace website · Call Trace on LinkedIn · plugin in OpenAI’s directory.